1014 lines
34 KiB
JavaScript
1014 lines
34 KiB
JavaScript
/*
|
||
* (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 (window)
|
||
{
|
||
/**
|
||
* Класс работающий с общей историей в совместном редактировании
|
||
* @param {AscCommon.CCollaborativeEditingBase} coEditing
|
||
* @constructor
|
||
*/
|
||
function CCollaborativeHistory(coEditing)
|
||
{
|
||
this.CoEditing = coEditing;
|
||
|
||
this.Changes = []; // Список всех изменений
|
||
this.ChangesSplitByPoints = [] // Список изменений разделенных по точкам
|
||
this.OwnRanges = []; // Диапазоны собственных изменений
|
||
|
||
this.SyncIndex = -1; // Позиция в массиве изменений, которые согласованы с сервером
|
||
this.curChangeIndex = -1; // Текущая позиция в массиве изменений разделенных по точкам
|
||
//this.StepTextPoint = undefined; //Позиция предыдущего состояния
|
||
|
||
this.textRecovery = null;
|
||
}
|
||
/**
|
||
* Разделяем изменения ревизии для отображения истории ревизии
|
||
*/
|
||
CCollaborativeHistory.prototype.SplitChangesByPoints = function ()
|
||
{
|
||
if (!this.Changes || !this.Changes || this.ChangesSplitByPoints.length !== 0)
|
||
return;
|
||
|
||
let arrCurrent = [0];
|
||
for (let i = 1; i < this.Changes.length; i++)
|
||
{
|
||
let oCurrentChange = this.Changes[i];
|
||
let oPrevChange = (i === 0)
|
||
? undefined
|
||
: this.Changes[i - 1];
|
||
|
||
if (!(oPrevChange && (oPrevChange instanceof AscCommon.CChangesTableIdDescription || oPrevChange.IsDescriptionChange() === oCurrentChange.IsDescriptionChange())))
|
||
{
|
||
arrCurrent.push(i);
|
||
}
|
||
}
|
||
|
||
arrCurrent.push(this.Changes.length);
|
||
this.ChangesSplitByPoints = arrCurrent;
|
||
this.curChangeIndex = arrCurrent.length - 1;
|
||
};
|
||
CCollaborativeHistory.prototype.clear = function()
|
||
{
|
||
this.Changes = [];
|
||
this.OwnRanges = [];
|
||
this.ChangesSplitByPoints = [];
|
||
|
||
this.SyncIndex = -1;
|
||
this.curChangeIndex = -1;
|
||
|
||
this.textRecovery = null;
|
||
};
|
||
/**
|
||
* Перемещаемся по истории ревизии на заданную точку
|
||
* @param {number} pointIndex - Позиция на которую необходимо переместится
|
||
* @constructor
|
||
*/
|
||
CCollaborativeHistory.prototype.NavigationRevisionHistoryByStep = function(pointIndex)
|
||
{
|
||
let logicDocument = this.CoEditing.GetLogicDocument();
|
||
if (!logicDocument || !logicDocument.IsDocumentEditor())
|
||
return false;
|
||
|
||
this.UndoDeletedTextRecovery();
|
||
this.SplitChangesByPoints();
|
||
if (this.curChangeIndex < 0)
|
||
return false;
|
||
|
||
pointIndex = Math.max(0, Math.min(pointIndex, this.ChangesSplitByPoints.length - 1));
|
||
if (pointIndex === this.curChangeIndex)
|
||
return false;
|
||
|
||
let changes;
|
||
if (this.curChangeIndex < pointIndex)
|
||
changes = this._RedoChanges(this.ChangesSplitByPoints[this.curChangeIndex], this.ChangesSplitByPoints[pointIndex]);
|
||
else
|
||
changes = this._UndoChanges(this.ChangesSplitByPoints[this.curChangeIndex], this.ChangesSplitByPoints[pointIndex]);
|
||
|
||
this.curChangeIndex = pointIndex;
|
||
logicDocument.RecalculateByChanges(changes);
|
||
return true;
|
||
};
|
||
CCollaborativeHistory.prototype.AddChange = function(change)
|
||
{
|
||
this.Changes.push(change);
|
||
};
|
||
CCollaborativeHistory.prototype.AddOwnChanges = function(ownChanges, deleteIndex)
|
||
{
|
||
// TODO: При удалении изменений не удаляются OwnRanges, которые могли ссылаться на эти изменения
|
||
// Надо проверить насколько это корректно
|
||
|
||
if (null !== deleteIndex)
|
||
this.Changes.length = this.SyncIndex + deleteIndex;
|
||
else
|
||
this.SyncIndex = this.Changes.length;
|
||
|
||
// TODO: Пока мы делаем это как одну точку, которую надо откатить. Надо пробежаться по массиву и разбить его
|
||
// по отдельным действиям. В принципе, данная схема срабатывает в быстром совместном редактировании,
|
||
// так что как правило две точки не успевают попасть в одно сохранение.
|
||
if (ownChanges.length > 0)
|
||
{
|
||
this.OwnRanges.push(new COwnRange(this.Changes.length, ownChanges.length));
|
||
this.Changes = this.Changes.concat(ownChanges);
|
||
}
|
||
};
|
||
CCollaborativeHistory.prototype.GetAllChanges = function()
|
||
{
|
||
return this.Changes;
|
||
};
|
||
CCollaborativeHistory.prototype.GetChangeCount = function()
|
||
{
|
||
return this.Changes.length;
|
||
};
|
||
CCollaborativeHistory.prototype.CanUndoGlobal = function()
|
||
{
|
||
return (this.Changes.length > 0);
|
||
};
|
||
CCollaborativeHistory.prototype.CanUndoOwn = function()
|
||
{
|
||
return (this.OwnRanges.length > 0)
|
||
};
|
||
CCollaborativeHistory.prototype._RedoChanges = function(startPos, endPos)
|
||
{
|
||
let changes = [];
|
||
for (let i = startPos; i < endPos; ++i)
|
||
{
|
||
let change = this.Changes[i];
|
||
if (change.IsContentChange())
|
||
{
|
||
let simpleChanges = change.ConvertToSimpleChanges();
|
||
for (let simpleIndex = 0; simpleIndex < simpleChanges.length; ++simpleIndex)
|
||
{
|
||
simpleChanges[simpleIndex].Redo();
|
||
changes.push(simpleChanges[simpleIndex]);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
change.Redo();
|
||
changes.push(change);
|
||
}
|
||
}
|
||
return changes;
|
||
};
|
||
CCollaborativeHistory.prototype._UndoChanges = function(startPos, endPos)
|
||
{
|
||
let changes = [];
|
||
for (let i = startPos - 1; i >= endPos; --i)
|
||
{
|
||
let change = this.Changes[i];
|
||
if (change.IsContentChange())
|
||
{
|
||
let simpleChanges = change.ConvertToSimpleChanges();
|
||
for (let simpleIndex = simpleChanges.length - 1; simpleIndex >= 0; --simpleIndex)
|
||
{
|
||
simpleChanges[simpleIndex].Undo();
|
||
changes.push(simpleChanges[simpleIndex]);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
change.Undo();
|
||
changes.push(change);
|
||
}
|
||
}
|
||
return changes;
|
||
};
|
||
/**
|
||
* Откатываем заданное количество действий
|
||
* @param {number} count
|
||
* @returns {[]} возвращаем массив откаченных действий
|
||
*/
|
||
CCollaborativeHistory.prototype.UndoGlobalChanges = function(count)
|
||
{
|
||
this.UndoDeletedTextRecovery();
|
||
|
||
count = Math.min(count, this.Changes.length);
|
||
|
||
if (!count)
|
||
return [];
|
||
|
||
let index = this.Changes.length - 1;
|
||
let changeArray = [];
|
||
while (index >= this.Changes.length - count)
|
||
{
|
||
let change = this.Changes[index--];
|
||
if (!change)
|
||
continue;
|
||
|
||
if (change.IsContentChange())
|
||
{
|
||
let simpleChanges = change.ConvertToSimpleChanges();
|
||
for (let simpleIndex = simpleChanges.length - 1; simpleIndex >= 0; --simpleIndex)
|
||
{
|
||
simpleChanges[simpleIndex].Undo();
|
||
changeArray.push(simpleChanges[simpleIndex]);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
change.Undo();
|
||
changeArray.push(change);
|
||
}
|
||
}
|
||
|
||
this.Changes.length = this.Changes.length - count;
|
||
return changeArray;
|
||
};
|
||
/**
|
||
* Отменяем все действия, попавшие в последнюю точку истории
|
||
* @returns {[]} возвращаем массив отмененных действий
|
||
*/
|
||
CCollaborativeHistory.prototype.UndoGlobalPoint = function()
|
||
{
|
||
this.UndoDeletedTextRecovery();
|
||
|
||
let count = 0;
|
||
for (let index = this.Changes.length - 1; index > 0; --index, ++count)
|
||
{
|
||
let change = this.Changes[index];
|
||
if (!change)
|
||
continue;
|
||
|
||
if (change.IsDescriptionChange())
|
||
{
|
||
count++;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return count ? this.UndoGlobalChanges(count) : [];
|
||
};
|
||
/**
|
||
* Получаем количество позиций истории в текущей ревизии
|
||
* @return {number}
|
||
* @constructor
|
||
*/
|
||
CCollaborativeHistory.prototype.GetGlobalPointCount = function()
|
||
{
|
||
this.SplitChangesByPoints();
|
||
return this.ChangesSplitByPoints.length;
|
||
};
|
||
/**
|
||
* Получаем текущую позицию в истории ревизии
|
||
* @return {number}
|
||
*/
|
||
CCollaborativeHistory.prototype.GetGlobalPointIndex = function()
|
||
{
|
||
this.SplitChangesByPoints();
|
||
return this.curChangeIndex;
|
||
};
|
||
/**
|
||
* Перемещаемся на нужную точку истории ревизии
|
||
* @param nPos - позиция в истории
|
||
* @return {boolean} - был ли произведен переход на данную позицию
|
||
*/
|
||
CCollaborativeHistory.prototype.MoveToPoint = function(nPos)
|
||
{
|
||
return this.NavigationRevisionHistoryByStep(nPos);
|
||
};
|
||
CCollaborativeHistory.prototype.InitTextRecover = function ()
|
||
{
|
||
if (this.textRecovery)
|
||
return;
|
||
|
||
let logicDocument = this.CoEditing.GetLogicDocument();
|
||
if (!logicDocument || !logicDocument.IsDocumentEditor())
|
||
return;
|
||
|
||
this.textRecovery = new AscCommon.DeletedTextRecovery(logicDocument);
|
||
};
|
||
/**
|
||
* Отображаем удаленный текст для данный точки в истории ревизии
|
||
* @return {boolean} - был ли отображен удаленный текст
|
||
*/
|
||
CCollaborativeHistory.prototype.RecoverDeletedText = function()
|
||
{
|
||
this.InitTextRecover();
|
||
return this.textRecovery.RecoverDeletedText();
|
||
};
|
||
/**
|
||
* Отменить отображение удаленного текста в данной точке истории ревизии
|
||
* @return {boolean}
|
||
*/
|
||
CCollaborativeHistory.prototype.UndoDeletedTextRecovery = function()
|
||
{
|
||
if (this.textRecovery)
|
||
return this.textRecovery.UndoRecoveredText();
|
||
|
||
return false;
|
||
};
|
||
CCollaborativeHistory.prototype.HaveDeletedTextRecovery = function()
|
||
{
|
||
return !!(this.textRecovery && this.textRecovery.HaveRecoveredText());
|
||
};
|
||
CCollaborativeHistory.prototype.GetCollaborativeMarks = function ()
|
||
{
|
||
return this.CoEditing.Get_CollaborativeMarks();
|
||
}
|
||
/**
|
||
* Отменяем собственные последние действия, прокатывая их через чужие
|
||
* @returns {[]} возвращаем массив новых действий
|
||
*/
|
||
CCollaborativeHistory.prototype.UndoOwnPoint = function()
|
||
{
|
||
// Формируем новую пачку действий, которые будут откатывать нужные нам действия
|
||
let reverseChanges = this.GetReverseOwnChanges();
|
||
if (reverseChanges.length <= 0)
|
||
{
|
||
//чтобы не было бесконечного saving(пересмотреть чтобы работало без saveChanges)
|
||
this.saveChanges([]);
|
||
return [];
|
||
}
|
||
|
||
for (let index = 0, count = reverseChanges.length; index < count; ++index)
|
||
{
|
||
reverseChanges[index].Load();
|
||
reverseChanges[index].CheckNeedRecalculate();
|
||
}
|
||
|
||
// Создаем точку в истории. Делаем действия через обычные функции (с отключенным пересчетом), которые пишут в
|
||
// историю. Сохраняем список изменений в новой точке, удаляем данную точку.
|
||
|
||
let historyPoint = this.CreateLocalHistoryPointByReverseChanges(reverseChanges);
|
||
let changesToSend = [], changesToRecalc = [];
|
||
for (let index = 0, count = historyPoint.Items.length; index < count; ++index)
|
||
{
|
||
let historyItem = historyPoint.Items[index];
|
||
let historyChange;
|
||
let historyClass = historyItem.Class;
|
||
|
||
//todo: Refactor Class and Data in spreadsheet to follow other editors design
|
||
if (AscCommon.History.Item_ToSerializable) {
|
||
historyChange = AscCommon.History.Item_ToSerializable(historyItem);
|
||
} else {
|
||
historyChange = historyItem.Data;
|
||
}
|
||
|
||
if (!historyClass || !(historyClass.Get_Id || historyClass.Class && historyClass.Class.Get_Id))
|
||
continue;
|
||
|
||
if (historyItem.Binary.Len) //spreadsheet local changes
|
||
{
|
||
let data = AscCommon.CCollaborativeChanges.ToBase64(historyItem.Binary.Pos, historyItem.Binary.Len);
|
||
changesToSend.push(data);
|
||
}
|
||
|
||
changesToRecalc.push(historyChange);
|
||
this.AddChange(historyChange);
|
||
}
|
||
AscCommon.History.Remove_LastPoint();
|
||
this.CoEditing.Clear_DCChanges();
|
||
|
||
this.saveChanges(changesToSend);
|
||
return changesToRecalc;
|
||
};
|
||
CCollaborativeHistory.prototype.saveChanges = function(changesToSend)
|
||
{
|
||
//separate function to override in excel
|
||
(Asc.editor || editor).CoAuthoringApi.saveChanges(changesToSend, null, null, false, this.CoEditing.getCollaborativeEditing());
|
||
};
|
||
CCollaborativeHistory.prototype.GetEmptyContentChanges = function()
|
||
{
|
||
let changes = [];
|
||
for (let index = this.Changes.length - 1; index >= 0; --index)
|
||
{
|
||
let tempChange = this.Changes[index];
|
||
|
||
if (tempChange.IsContentChange() && tempChange.GetItemsCount() <= 0)
|
||
changes.push(tempChange);
|
||
}
|
||
return changes;
|
||
};
|
||
/**
|
||
* Проверяем, что последнее изменение в документе - это ввод заданного символа
|
||
* @param {AscWord.CRun} run
|
||
* @param {number} inRunPos
|
||
* @param {?number} codePoint
|
||
* @returns {boolean}
|
||
*/
|
||
CCollaborativeHistory.prototype.checkAsYouTypeEnterText = function(run, inRunPos, codePoint)
|
||
{
|
||
if (!this.Changes.length || !this.OwnRanges.length)
|
||
return false;
|
||
|
||
let lastOwnRange = this.OwnRanges[this.OwnRanges.length - 1];
|
||
if (this.Changes.length !== lastOwnRange.Position + lastOwnRange.Length)
|
||
return false;
|
||
|
||
let lastChange = this.Changes[this.Changes.length - 1];
|
||
return (AscDFH.historyitem_ParaRun_AddItem === lastChange.Type
|
||
&& lastChange.Class === run
|
||
&& lastChange.Pos === inRunPos - 1
|
||
&& lastChange.Items.length
|
||
&& (undefined === codePoint || lastChange.Items[0].GetCodePoint() === codePoint));
|
||
};
|
||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// Private area
|
||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
CCollaborativeHistory.prototype.GetReverseOwnChanges = function()
|
||
{
|
||
// На первом шаге мы заданнуюю пачку изменений коммутируем с последними измениями. Смотрим на то какой набор
|
||
// изменений у нас получается.
|
||
// Объектная модель у нас простая: класс, в котором возможно есть массив элементов(тоже классов), у которого воможно
|
||
// есть набор свойств. Поэтому у нас ровно 2 типа изменений: изменения внутри массива элементов, либо изменения
|
||
// свойств. Изменения этих двух типов коммутируют между собой, изменения разных классов тоже коммутируют.
|
||
|
||
if (this.OwnRanges.length <= 0)
|
||
return [];
|
||
|
||
let range = this.OwnRanges[this.OwnRanges.length - 1];
|
||
let nPosition = range.Position;
|
||
let nCount = range.Length;
|
||
|
||
let arrReverseChanges = [];
|
||
for (let nIndex = nCount - 1; nIndex >= 0; --nIndex)
|
||
{
|
||
let oChange = this.Changes[nPosition + nIndex];
|
||
if (!oChange)
|
||
continue;
|
||
|
||
let oClass = oChange.GetClass();
|
||
if (oChange.IsContentChange())
|
||
{
|
||
let _oChange = oChange.Copy();
|
||
let simpleChanges = _oChange.ConvertToSimpleChanges();
|
||
for (let simpleIndex = simpleChanges.length - 1; simpleIndex >= 0; --simpleIndex)
|
||
{
|
||
let simpleChange = simpleChanges[simpleIndex];
|
||
if (this.CommuteContentChange(simpleChange, nPosition + nCount))
|
||
{
|
||
let oReverseChange = simpleChange.CreateReverseChange();
|
||
if (oReverseChange)
|
||
{
|
||
arrReverseChanges.push(oReverseChange);
|
||
oReverseChange.SetReverted(true);
|
||
}
|
||
}
|
||
}
|
||
oChange.SetReverted(true);
|
||
}
|
||
else if (oChange.IsSpreadsheetChange())
|
||
{
|
||
let _oChange = oChange.Copy();
|
||
//удобнее сначала создавать обратное изменение
|
||
let oReverseChange = _oChange.CreateReverseChange();
|
||
if (oReverseChange) {
|
||
if (this.CommuteRelated(oClass, oReverseChange, nPosition + nCount))
|
||
{
|
||
oReverseChange.SetReverted(true);
|
||
arrReverseChanges.push(oReverseChange);
|
||
}
|
||
else
|
||
{
|
||
//todo для автофигур не надо скрывать всю точку
|
||
//в таблицах не принимается вся точка
|
||
//например при вставка столбца копируется заливка соседнего столбца
|
||
arrReverseChanges = [];
|
||
for (let i = nCount - 1; i > nIndex; --i)
|
||
{
|
||
this.Changes[nPosition + i].SetReverted(false);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
else if(null !== oReverseChange)
|
||
{
|
||
//ничего не делаем если есть изменения которые не готовы
|
||
arrReverseChanges = [];
|
||
for (let i = nCount - 1; i > nIndex; --i)
|
||
{
|
||
this.Changes[nPosition + i].SetReverted(false);
|
||
}
|
||
break;
|
||
}
|
||
oChange.SetReverted(true);
|
||
}
|
||
else
|
||
{
|
||
let _oChange = oChange; // TODO: Тут надо бы сделать копирование
|
||
|
||
if (this.CommutePropertyChange(oClass, _oChange, nPosition + nCount))
|
||
{
|
||
let oReverseChange = _oChange.CreateReverseChange();
|
||
if (oReverseChange)
|
||
{
|
||
arrReverseChanges.push(oReverseChange);
|
||
oReverseChange.SetReverted(true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
this.OwnRanges.length = this.OwnRanges.length - 1;
|
||
|
||
return arrReverseChanges;
|
||
};
|
||
CCollaborativeHistory.prototype.CommuteContentChange = function(oChange, nStartPosition, arrChanges)
|
||
{
|
||
var arrChangesForProceed = this.Changes;
|
||
if (arrChanges && arrChanges.length > 0)
|
||
arrChangesForProceed = arrChanges;
|
||
|
||
var arrActions = oChange.ConvertToSimpleActions();
|
||
var arrCommutateActions = [];
|
||
|
||
for (var nActionIndex = arrActions.length - 1; nActionIndex >= 0; --nActionIndex)
|
||
{
|
||
var oAction = arrActions[nActionIndex];
|
||
var oResult = oAction;
|
||
|
||
for (var nIndex = nStartPosition, nOverallCount = arrChangesForProceed.length; nIndex < nOverallCount; ++nIndex)
|
||
{
|
||
var oTempChange = arrChangesForProceed[nIndex];
|
||
if (!oTempChange)
|
||
continue;
|
||
|
||
if (oChange.IsRelated(oTempChange) && true !== oTempChange.IsReverted())
|
||
{
|
||
var arrOtherActions = oTempChange.ConvertToSimpleActions();
|
||
for (var nIndex2 = 0, nOtherActionsCount2 = arrOtherActions.length; nIndex2 < nOtherActionsCount2; ++nIndex2)
|
||
{
|
||
var oOtherAction = arrOtherActions[nIndex2];
|
||
|
||
if (false === this.CommuteContentChangeActions(oAction, oOtherAction))
|
||
{
|
||
arrOtherActions.splice(nIndex2, 1);
|
||
oResult = null;
|
||
break;
|
||
}
|
||
}
|
||
|
||
oTempChange.ConvertFromSimpleActions(arrOtherActions);
|
||
}
|
||
|
||
if (!oResult)
|
||
break;
|
||
|
||
}
|
||
|
||
if (null !== oResult)
|
||
arrCommutateActions.push(oResult);
|
||
}
|
||
|
||
if (arrCommutateActions.length <= 0)
|
||
return false;
|
||
|
||
oChange.ConvertFromSimpleActions(arrCommutateActions, true);
|
||
return true;
|
||
};
|
||
CCollaborativeHistory.prototype.CommuteContentChangeActions = function(oActionL, oActionR)
|
||
{
|
||
if (oActionL.Add)
|
||
{
|
||
if (oActionR.Add)
|
||
{
|
||
if (oActionL.Pos >= oActionR.Pos)
|
||
oActionL.Pos++;
|
||
else
|
||
oActionR.Pos--;
|
||
}
|
||
else
|
||
{
|
||
if (oActionL.Pos > oActionR.Pos)
|
||
oActionL.Pos--;
|
||
else if (oActionL.Pos === oActionR.Pos)
|
||
return false;
|
||
else
|
||
oActionR.Pos--;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (oActionR.Add)
|
||
{
|
||
if (oActionL.Pos >= oActionR.Pos)
|
||
oActionL.Pos++;
|
||
else
|
||
oActionR.Pos++;
|
||
}
|
||
else
|
||
{
|
||
if (oActionL.Pos > oActionR.Pos)
|
||
oActionL.Pos--;
|
||
else
|
||
oActionR.Pos++;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
};
|
||
CCollaborativeHistory.prototype.CommutePropertyChange = function(oClass, oChange, nStartPosition)
|
||
{
|
||
// В GoogleDocs если 2 пользователя исправляют одно и тоже свойство у одного и того же класса, тогда Undo работает
|
||
// у обоих. Например, первый выставляет параграф по центру (изначально по левому), второй после этого по правому
|
||
// краю. Тогда на Undo первого пользователя возвращает параграф по левому краю, а у второго по центру, неважно в
|
||
// какой последовательности они вызывают Undo.
|
||
// Далем как у них: т.е. изменения свойств мы всегда откатываем, даже если данное свойсво менялось в последующих
|
||
// изменениях.
|
||
|
||
// Здесь вариант: свойство не откатываем, если оно менялось в одном из последующих действий. (для работы этого
|
||
// варианта нужно реализовать функцию IsRelated у всех изменений).
|
||
|
||
// // Значит это изменение свойства. Пробегаемся по всем следующим изменениям и смотрим, менялось ли такое
|
||
// // свойство у данного класса, если да, тогда данное изменение невозможно скоммутировать.
|
||
// for (var nIndex = nStartPosition, nOverallCount = this.Changes.length; nIndex < nOverallCount; ++nIndex)
|
||
// {
|
||
// var oTempChange = this.Changes[nIndex];
|
||
// if (!oTempChange || !oTempChange.IsChangesClass || !oTempChangeIsChangesClass())
|
||
// continue;
|
||
//
|
||
// if (oChange.IsRelated(oTempChange))
|
||
// return false;
|
||
// }
|
||
|
||
|
||
return true;
|
||
};
|
||
|
||
CCollaborativeHistory.prototype.CommuteRelated = function(oClass, oChange, nStartPosition)
|
||
{
|
||
return true;
|
||
}
|
||
CCollaborativeHistory.prototype.CreateLocalHistoryPointByReverseChanges = function(reverseChanges)
|
||
{
|
||
let localHistory = AscCommon.History;
|
||
|
||
let pointIndex = localHistory.CreateNewPointToCollectChanges(AscDFH.historydescription_Collaborative_Undo);
|
||
|
||
// До вызова данного метода мы все изменения reverseChanges прогнали через Load, где
|
||
// изменения позиций из-за этих изменений уже были отмечены
|
||
this.CoEditing.StopTrackingPositions();
|
||
for (let index = 0, count = reverseChanges.length; index < count; ++index)
|
||
{
|
||
let change = reverseChanges[index];
|
||
localHistory.Add(change);
|
||
}
|
||
this.CoEditing.StartTrackingPositions();
|
||
|
||
this.CorrectReveredChanges(reverseChanges);
|
||
|
||
localHistory.Update_PointInfoItem(pointIndex, pointIndex, pointIndex, 0, null);
|
||
localHistory.ConvertPointItemsToSimpleChanges(pointIndex);
|
||
|
||
return localHistory.Points[pointIndex];
|
||
};
|
||
CCollaborativeHistory.prototype.CorrectReveredChanges = function(arrReverseChanges)
|
||
{
|
||
let oLogicDocument = this.CoEditing.GetLogicDocument();
|
||
|
||
// Может так случиться, что в каких-то классах DocumentContent удалились все элементы, либо
|
||
// в классе Paragraph удалился знак конца параграфа. Нам необходимо проверить все классы на корректность, и если
|
||
// нужно, добавить дополнительные изменения.
|
||
|
||
var mapDrawings = {};
|
||
var mapDocumentContents = {};
|
||
var mapParagraphs = {};
|
||
var mapRuns = {};
|
||
var mapTables = {};
|
||
var mapGrObjects = {};
|
||
var mapSlides = {};
|
||
var mapLayouts = {};
|
||
var mapTimings = {};
|
||
var bChangedLayout = false;
|
||
var bAddSlides = false;
|
||
var mapAddedSlides = {};
|
||
var mapCommentsToDelete = {};
|
||
const mapSmartArtShapes = {};
|
||
|
||
for (let nIndex = 0, nCount = arrReverseChanges.length; nIndex < nCount; ++nIndex)
|
||
{
|
||
var oChange = arrReverseChanges[nIndex];
|
||
var oClass = oChange.GetClass();
|
||
|
||
if (oClass instanceof AscCommonWord.CDocument || oClass instanceof AscCommonWord.CDocumentContent)
|
||
{
|
||
mapDocumentContents[oClass.Get_Id()] = oClass;
|
||
}
|
||
else if (oClass instanceof AscWord.Paragraph)
|
||
{
|
||
mapParagraphs[oClass.Get_Id()] = oClass;
|
||
}
|
||
else if (oClass.IsParagraphContentElement && true === oClass.IsParagraphContentElement() && true === oChange.IsContentChange() && oClass.GetParagraph())
|
||
{
|
||
const oParagraph = oClass.GetParagraph();
|
||
mapParagraphs[oParagraph.Get_Id()] = oParagraph;
|
||
if (oClass instanceof AscCommonWord.ParaRun)
|
||
mapRuns[oClass.Get_Id()] = oClass;
|
||
const oSmartArtShape = oParagraph.IsInsideSmartArtShape(true);
|
||
if (oSmartArtShape)
|
||
{
|
||
mapSmartArtShapes[oSmartArtShape.Get_Id()] = oSmartArtShape;
|
||
}
|
||
}
|
||
else if (oClass && oClass.parent && oClass.parent instanceof AscCommonWord.ParaDrawing)
|
||
{
|
||
mapDrawings[oClass.parent.Get_Id()] = oClass.parent;
|
||
}
|
||
else if (oClass instanceof AscCommonWord.ParaDrawing)
|
||
{
|
||
mapDrawings[oClass.Get_Id()] = oClass;
|
||
}
|
||
else if (oClass instanceof AscCommonWord.ParaRun)
|
||
{
|
||
mapRuns[oClass.Get_Id()] = oClass;
|
||
const oSmartArtShape = oClass.IsInsideSmartArtShape(true);
|
||
if (oSmartArtShape)
|
||
{
|
||
mapSmartArtShapes[oSmartArtShape.Get_Id()] = oSmartArtShape;
|
||
}
|
||
}
|
||
else if (oClass instanceof AscCommonWord.CTable)
|
||
{
|
||
mapTables[oClass.Get_Id()] = oClass;
|
||
}
|
||
else if (oClass instanceof AscFormat.CShape
|
||
|| oClass instanceof AscFormat.CImageShape
|
||
|| oClass instanceof AscFormat.CChartSpace
|
||
|| oClass instanceof AscFormat.CGroupShape
|
||
|| oClass instanceof AscFormat.CGraphicFrame)
|
||
{
|
||
mapGrObjects[oClass.Get_Id()] = oClass;
|
||
let oParent = oClass.parent;
|
||
if (oParent && oParent.timing)
|
||
{
|
||
mapTimings[oParent.timing.Get_Id()] = oParent.timing;
|
||
}
|
||
}
|
||
else if (typeof AscCommonSlide !== "undefined" && AscCommonSlide.Slide && oClass instanceof AscCommonSlide.Slide)
|
||
{
|
||
mapSlides[oClass.Get_Id()] = oClass;
|
||
if (oClass.timing)
|
||
{
|
||
mapTimings[oClass.timing.Get_Id()] = oClass.timing;
|
||
}
|
||
}
|
||
else if (typeof AscCommonSlide !== "undefined" && AscCommonSlide.SlideLayout && oClass instanceof AscCommonSlide.SlideLayout)
|
||
{
|
||
mapLayouts[oClass.Get_Id()] = oClass;
|
||
if (oClass.timing)
|
||
{
|
||
mapTimings[oClass.timing.Get_Id()] = oClass.timing;
|
||
}
|
||
bChangedLayout = true;
|
||
}
|
||
else if (typeof AscCommonSlide !== "undefined" && AscCommonSlide.CPresentation && oClass instanceof AscCommonSlide.CPresentation)
|
||
{
|
||
if (oChange.Type === AscDFH.historyitem_Presentation_RemoveSlide || oChange.Type === AscDFH.historyitem_Presentation_AddSlide)
|
||
{
|
||
bAddSlides = true;
|
||
for (var i = 0; i < oChange.Items.length; ++i)
|
||
{
|
||
mapAddedSlides[oChange.Items[i].Get_Id()] = oChange.Items[i];
|
||
}
|
||
}
|
||
}
|
||
else if (AscDFH.historyitem_ParaComment_CommentId === oChange.Type)
|
||
{
|
||
mapCommentsToDelete[oChange.New] = oClass;
|
||
}
|
||
else if (oClass.isAnimObject)
|
||
{
|
||
let oTiming = oClass.getTiming();
|
||
if (oTiming)
|
||
{
|
||
mapTimings[oTiming.Get_Id()] = oTiming;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if (bAddSlides)
|
||
{
|
||
for (var i = oLogicDocument.Slides.length - 1; i > -1; --i)
|
||
{
|
||
if (mapAddedSlides[oLogicDocument.Slides[i].Get_Id()] && !oLogicDocument.Slides[i].Layout)
|
||
{
|
||
oLogicDocument.removeSlide(i);
|
||
}
|
||
}
|
||
}
|
||
|
||
for (var sId in mapSlides)
|
||
{
|
||
if (mapSlides.hasOwnProperty(sId))
|
||
{
|
||
mapSlides[sId].correctContent();
|
||
}
|
||
}
|
||
|
||
if (bChangedLayout)
|
||
{
|
||
for (var i = oLogicDocument.Slides.length - 1; i > -1; --i)
|
||
{
|
||
var Layout = oLogicDocument.Slides[i].Layout;
|
||
if (!Layout || mapLayouts[Layout.Get_Id()])
|
||
{
|
||
if (!oLogicDocument.Slides[i].CheckLayout())
|
||
{
|
||
oLogicDocument.removeSlide(i);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
for (var sId in mapGrObjects)
|
||
{
|
||
var oShape = mapGrObjects[sId];
|
||
if (!oShape.checkCorrect())
|
||
{
|
||
oShape.setBDeleted(true);
|
||
if (oShape.group)
|
||
{
|
||
oShape.group.removeFromSpTree(oShape.Get_Id());
|
||
}
|
||
else if (AscFormat.Slide && (oShape.parent instanceof AscFormat.Slide))
|
||
{
|
||
oShape.parent.removeFromSpTreeById(oShape.Get_Id());
|
||
}
|
||
else if (AscCommonWord.ParaDrawing)
|
||
{
|
||
if(oShape.parent instanceof AscCommonWord.ParaDrawing)
|
||
{
|
||
mapDrawings[oShape.parent.Get_Id()] = oShape.parent;
|
||
}
|
||
if(oShape.oldParent && oShape.oldParent instanceof AscCommonWord.ParaDrawing)
|
||
{
|
||
mapDrawings[oShape.oldParent.Get_Id()] = oShape.oldParent;
|
||
oShape.oldParent = undefined;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (oShape.resetGroups)
|
||
{
|
||
oShape.resetGroups();
|
||
}
|
||
}
|
||
}
|
||
var oDrawing;
|
||
for (var sId in mapDrawings)
|
||
{
|
||
if (mapDrawings.hasOwnProperty(sId))
|
||
{
|
||
oDrawing = mapDrawings[sId];
|
||
if (!oDrawing.CheckCorrect())
|
||
{
|
||
var oParentParagraph = oDrawing.Get_ParentParagraph();
|
||
let oParentRun = oDrawing.Get_Run();
|
||
if(oParentRun)
|
||
{
|
||
mapRuns[oParentRun.Id] = oParentRun;
|
||
}
|
||
oDrawing.PreDelete();
|
||
oDrawing.Remove_FromDocument(false);
|
||
if (oParentParagraph)
|
||
{
|
||
mapParagraphs[oParentParagraph.Get_Id()] = oParentParagraph;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (var sId in mapRuns)
|
||
{
|
||
if (mapRuns.hasOwnProperty(sId))
|
||
{
|
||
var oRun = mapRuns[sId];
|
||
for (var nIndex = oRun.Content.length - 1; nIndex > -1; --nIndex)
|
||
{
|
||
if (oRun.Content[nIndex] instanceof AscCommonWord.ParaDrawing)
|
||
{
|
||
if (!oRun.Content[nIndex].CheckCorrect())
|
||
{
|
||
oRun.Remove_FromContent(nIndex, 1, false);
|
||
if (oRun.Paragraph)
|
||
{
|
||
mapParagraphs[oRun.Paragraph.Get_Id()] = oRun.Paragraph;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
for (let sId in mapSmartArtShapes)
|
||
{
|
||
const oSmartArtShape = mapSmartArtShapes[sId];
|
||
oSmartArtShape.correctSmartArtUndo();
|
||
}
|
||
for (var sId in mapTables)
|
||
{
|
||
var oTable = mapTables[sId];
|
||
for (var nCurRow = oTable.Content.length - 1; nCurRow >= 0; --nCurRow)
|
||
{
|
||
var oRow = oTable.Get_Row(nCurRow);
|
||
if (oRow.Get_CellsCount() <= 0)
|
||
oTable.private_RemoveRow(nCurRow);
|
||
}
|
||
|
||
if (oTable.Parent instanceof AscCommonWord.CDocument || oTable.Parent instanceof AscCommonWord.CDocumentContent)
|
||
mapDocumentContents[oTable.Parent.Get_Id()] = oTable.Parent;
|
||
}
|
||
|
||
for (var sId in mapDocumentContents)
|
||
{
|
||
var oDocumentContent = mapDocumentContents[sId];
|
||
var nContentLen = oDocumentContent.Content.length;
|
||
for (var nIndex = nContentLen - 1; nIndex >= 0; --nIndex)
|
||
{
|
||
var oElement = oDocumentContent.Content[nIndex];
|
||
if ((AscCommonWord.type_Paragraph === oElement.GetType() || AscCommonWord.type_Table === oElement.GetType()) && oElement.Content.length <= 0)
|
||
{
|
||
oDocumentContent.Remove_FromContent(nIndex, 1);
|
||
}
|
||
}
|
||
|
||
nContentLen = oDocumentContent.Content.length;
|
||
if (nContentLen <= 0 || AscCommonWord.type_Paragraph !== oDocumentContent.Content[nContentLen - 1].GetType())
|
||
{
|
||
var oNewParagraph = new AscWord.Paragraph(oDocumentContent, 0, 0, 0, 0, 0, false);
|
||
oDocumentContent.Add_ToContent(nContentLen, oNewParagraph);
|
||
}
|
||
}
|
||
|
||
for (var sId in mapParagraphs)
|
||
{
|
||
var oParagraph = mapParagraphs[sId];
|
||
oParagraph.CheckParaEnd();
|
||
oParagraph.Correct_Content(null, null, true);
|
||
}
|
||
|
||
for (var sId in mapTimings)
|
||
{
|
||
if (mapTimings.hasOwnProperty(sId))
|
||
{
|
||
let oTiming = mapTimings[sId];
|
||
oTiming.checkCorrect();
|
||
}
|
||
}
|
||
if (oLogicDocument && oLogicDocument.IsDocumentEditor())
|
||
{
|
||
for (var sCommentId in mapCommentsToDelete)
|
||
{
|
||
oLogicDocument.RemoveComment(sCommentId, false, false);
|
||
}
|
||
}
|
||
};
|
||
//------------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* Отрезок собственных изменений в общем массиве
|
||
* @param position
|
||
* @param length
|
||
* @constructor
|
||
*/
|
||
function COwnRange(position, length)
|
||
{
|
||
this.Position = position;
|
||
this.Length = length;
|
||
}
|
||
|
||
//--------------------------------------------------------export----------------------------------------------------
|
||
window['AscCommon'] = window['AscCommon'] || {};
|
||
window['AscCommon'].CCollaborativeHistory = CCollaborativeHistory;
|
||
|
||
CCollaborativeHistory.prototype["GetGlobalPointCount"] = CCollaborativeHistory.prototype.GetGlobalPointCount;
|
||
CCollaborativeHistory.prototype["getGlobalPointIndex"] = CCollaborativeHistory.prototype.GetGlobalPointIndex;
|
||
CCollaborativeHistory.prototype["moveToPoint"] = CCollaborativeHistory.prototype.MoveToPoint;
|
||
CCollaborativeHistory.prototype["recoverDeletedText"] = CCollaborativeHistory.prototype.RecoverDeletedText;
|
||
CCollaborativeHistory.prototype["undoDeletedTextRecovery"] = CCollaborativeHistory.prototype.UndoDeletedTextRecovery;
|
||
|
||
})(window);
|