collabora-online/browser/src/layer/tile/CommentListSection.ts

2143 lines
78 KiB
TypeScript

/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* See CanvasSectionContainer.ts for explanations. */
L.Map.include({
insertComment: function() {
if (cool.Comment.isAnyEdit()) {
cool.CommentSection.showCommentEditingWarning();
return;
}
var avatar = undefined;
var author = this.getViewName(this._docLayer._viewId);
if (author in this._viewInfoByUserName) {
avatar = this._viewInfoByUserName[author].userextrainfo.avatar;
}
this._docLayer.newAnnotation({
text: '',
textrange: '',
author: author,
dateTime: new Date().toDateString(),
id: 'new', // 'new' only when added by us
avatar: avatar
});
},
showResolvedComments: function(on: any) {
var unoCommand = '.uno:ShowResolvedAnnotations';
this.sendUnoCommand(unoCommand);
app.sectionContainer.getSectionWithName(L.CSections.CommentList.name).setViewResolved(on);
this.uiManager.setSavedState('ShowResolved', on ? true : false);
}
});
declare var L: any;
declare var app: any;
declare var _: any;
namespace cool {
export class CommentSection extends CanvasSectionObject {
map: any;
sectionProperties: {
commentList: Array<Comment>;
selectedComment: Comment | null;
calcCurrentComment: Comment | null;
marginY: number;
offset: number;
width: number;
commentWidth: number;
collapsedMarginToTheEdge: number;
deflectionOfSelectedComment: number;
commentsAreListed: boolean;
[key: string]: any;
};
static autoSavedComment: cool.Comment;
static commentWasAutoAdded: boolean;
// To associate comment id with its index in commentList array.
private idIndexMap: Map<any, number>;
constructor () {
super({
name: L.CSections.CommentList.name,
backgroundColor: app.sectionContainer.clearColor,
borderColor: null,
anchor: [],
position: [0, 0],
size: [0, 0],
expand: 'bottom',
showSection: true,
processingOrder: L.CSections.CommentList.processingOrder,
drawingOrder: L.CSections.CommentList.drawingOrder,
zIndex: L.CSections.CommentList.zIndex,
interactable: false,
sectionProperties: {},
});
this.map = L.Map.THIS;
this.anchor = ['top', 'right'];
this.sectionProperties.docLayer = this.map._docLayer;
this.sectionProperties.commentList = new Array(0);
this.sectionProperties.selectedComment = null;
this.sectionProperties.arrow = null;
this.sectionProperties.showResolved = null;
this.sectionProperties.marginY = 10 * app.dpiScale;
this.sectionProperties.offset = 5 * app.dpiScale;
this.sectionProperties.layoutTimer = null;
this.sectionProperties.width = Math.round(1 * app.dpiScale); // Configurable variable.
this.sectionProperties.scrollAnnotation = null; // For impress, when 1 or more comments exist.
this.sectionProperties.commentWidth = 200 * 1.3; // CSS pixels.
this.sectionProperties.collapsedMarginToTheEdge = (<any>window).mode.isTablet() ? 120: 70; // CSS pixels.
this.sectionProperties.deflectionOfSelectedComment = 160; // CSS pixels.
this.sectionProperties.calcCurrentComment = null; // We don't automatically show a Calc comment when cursor is on its cell. But we remember it to show if user presses Alt+C keys.
// This (commentsAreListed) variable means that comments are shown as a list on the right side of the document.
this.sectionProperties.commentsAreListed = (this.sectionProperties.docLayer._docType === 'text' || this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') && !(<any>window).mode.isMobile();
this.idIndexMap = new Map<any, number>();
}
public onInitialize (): void {
this.checkCollapseState();
this.map.on('RedlineAccept', this.onRedlineAccept, this);
this.map.on('RedlineReject', this.onRedlineReject, this);
this.map.on('updateparts', this.showHideComments, this);
this.map.on('AnnotationScrollUp', this.onAnnotationScrollUp, this);
this.map.on('AnnotationScrollDown', this.onAnnotationScrollDown, this);
this.map.on('commandstatechanged', function (event: any) {
if (event.commandName === '.uno:ShowResolvedAnnotations')
this.setViewResolved(event.state === 'true');
else if (event.commandName === '.uno:ShowTrackedChanges' && event.state === 'true')
app.socket.sendMessage('commandvalues command=.uno:ViewAnnotations');
}, this);
this.map.on('zoomend', function() {
this.checkCollapseState();
this.layout(true);
}, this);
this.backgroundColor = this.containerObject.getClearColor();
this.initializeContextMenus();
if ((<any>window).mode.isMobile()) {
this.setShowSection(false);
this.size[0] = 0;
}
}
public static showCommentEditingWarning (): void {
L.Map.THIS.uiManager.showInfoModal('annotation-editing', _('A comment is being edited'),
_('Please save or discard the comment currently being edited.'), null, _('Close'));
}
private checkCollapseState(): void {
if (!(<any>window).mode.isMobile() && this.sectionProperties.docLayer._docType !== 'spreadsheet') {
if (this.shouldCollapse()) {
this.sectionProperties.deflectionOfSelectedComment = 180;
this.setCollapsed();
}
else {
this.sectionProperties.deflectionOfSelectedComment = 70;
this.setExpanded();
}
if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing')
this.showHideComments();
}
}
private findNextPartWithComment (currentPart: number): number {
for (var i = 0; i < this.sectionProperties.commentList.length; i++) {
if (this.sectionProperties.commentList[i].sectionProperties.partIndex > currentPart) {
return this.sectionProperties.commentList[i].sectionProperties.partIndex;
}
}
return -1;
}
private findPreviousPartWithComment (currentPart: number): number {
for (var i = this.sectionProperties.commentList.length - 1; i > -1; i--) {
if (this.sectionProperties.commentList[i].sectionProperties.partIndex < currentPart) {
return this.sectionProperties.commentList[i].sectionProperties.partIndex;
}
}
return -1;
}
public onAnnotationScrollDown (): void {
var index = this.findNextPartWithComment(this.sectionProperties.docLayer._selectedPart);
if (index >= 0) {
this.map.setPart(index);
}
}
public onAnnotationScrollUp (): void {
var index = this.findPreviousPartWithComment(this.sectionProperties.docLayer._selectedPart);
if (index >= 0) {
this.map.setPart(index);
}
}
private checkSize (): void {
// When there is no comment || file is a spreadsheet || view type is mobile, we set this section's size to [0, 0].
if (this.sectionProperties.docLayer._docType === 'spreadsheet' || (<any>window).mode.isMobile() || this.sectionProperties.commentList.length === 0)
{
if (this.sectionProperties.docLayer._docType === 'presentation' && this.sectionProperties.scrollAnnotation) {
this.map.removeControl(this.sectionProperties.scrollAnnotation);
this.sectionProperties.scrollAnnotation = null;
}
}
else if (this.sectionProperties.docLayer._docType === 'presentation') { // If there are comments but none of them are on the selected part.
if (!this.sectionProperties.scrollAnnotation) {
this.sectionProperties.scrollAnnotation = L.control.scrollannotation();
this.sectionProperties.scrollAnnotation.addTo(this.map);
}
}
}
public setCollapsed(): void {
this.isCollapsed = true;
this.unselect();
for (var i: number = 0; i < this.sectionProperties.commentList.length; i++) {
if (this.sectionProperties.commentList[i].sectionProperties.data.id !== 'new')
this.sectionProperties.commentList[i].setCollapsed();
if (this.sectionProperties.commentList[i].isRootComment())
this.collapseReplies(i, this.sectionProperties.commentList[i].sectionProperties.data.id);
}
}
public setExpanded(): void {
this.isCollapsed = false;
for (var i: number = 0; i < this.sectionProperties.commentList.length; i++) {
this.sectionProperties.commentList[i].setExpanded();
}
}
private calculateAvailableSpace() {
var availableSpace = (this.containerObject.getDocumentAnchorSection().size[0] - app.file.size.pixels[0]) * 0.5;
availableSpace = Math.round(availableSpace / app.dpiScale);
return availableSpace;
}
public shouldCollapse (): boolean {
if (!this.containerObject.getDocumentAnchorSection() || this.sectionProperties.docLayer._docType === 'spreadsheet' || (<any>window).mode.isMobile())
return false;
return this.calculateAvailableSpace() < this.sectionProperties.commentWidth;
}
public hideAllComments (): void {
for (var i: number = 0; i < this.sectionProperties.commentList.length; i++) {
this.sectionProperties.commentList[i].hide();
var part = this.sectionProperties.docLayer._selectedPart;
if (this.sectionProperties.docLayer._docType === 'spreadsheet') {
// Change drawing order so they don't prevent each other from being shown.
if (parseInt(this.sectionProperties.commentList[i].sectionProperties.data.tab) === part) {
this.sectionProperties.commentList[i].drawingOrder = 2;
}
else {
this.sectionProperties.commentList[i].drawingOrder = 1;
}
}
}
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.containerObject.applyDrawingOrders();
}
// Mobile.
private getChildren(comment: any, array: Array<any>) {
for (var i = 0; i < comment.sectionProperties.children.length; i++) {
array.push(comment.sectionProperties.children[i]);
if (comment.sectionProperties.children[i].sectionProperties.children.length > 0)
this.getChildren(comment.sectionProperties.children[i], array);
}
}
// Mobile.
private getCommentListOneDimensionalArray() {
// 1 dimensional array of ordered comments.
var openArray = [];
for (var i = 0; i < this.sectionProperties.commentList.length; i++) {
if (this.sectionProperties.commentList[i].isRootComment()) {
openArray.push(this.sectionProperties.commentList[i]);
if (this.sectionProperties.commentList[i].sectionProperties.children.length > 0)
this.getChildren(this.sectionProperties.commentList[i], openArray);
}
}
return openArray;
}
private createCommentStructureWriter (menuStructure: any, threadOnly: any): void {
var rootComment, comment;
var commentList = this.getCommentListOneDimensionalArray();
var showResolved = this.sectionProperties.showResolved;
if (threadOnly) {
if (!threadOnly.sectionProperties.data.trackchange && threadOnly.sectionProperties.data.parent !== '0')
threadOnly = commentList[this.getIndexOf(threadOnly.sectionProperties.data.parent)];
}
for (var i = 0; i < commentList.length; i++) {
if (commentList[i].isRootComment() || commentList[i].sectionProperties.data.trackchange) {
var commentThread = [];
do {
comment = {
id: 'comment' + commentList[i].sectionProperties.data.id,
enable: true,
data: commentList[i].sectionProperties.data,
type: 'comment',
text: commentList[i].sectionProperties.data.text,
annotation: commentList[i],
children: []
};
if (showResolved || comment.data.resolved !== 'true') {
commentThread.unshift(comment);
}
i++;
} while (commentList[i] && commentList[i].sectionProperties.data.parent !== '0');
i--;
if (commentThread.length > 0)
{
rootComment = {
id: commentThread[commentThread.length - 1].id,
enable: true,
data: commentThread[commentThread.length - 1].data,
type: 'rootcomment',
text: commentThread[commentThread.length - 1].data.text,
annotation: commentThread[commentThread.length - 1].annotation,
children: commentThread
};
var matchingThread = threadOnly && threadOnly.sectionProperties.data.id === commentThread[0].data.id;
if (matchingThread)
menuStructure['children'] = commentThread;
else if (!threadOnly)
menuStructure['children'].push(rootComment);
}
}
}
}
public createCommentStructureImpress (menuStructure: any, threadOnly: any): void {
var rootComment;
for (var i in this.sectionProperties.commentList) {
var matchingThread = !threadOnly || (threadOnly && threadOnly.sectionProperties.data.id === this.sectionProperties.commentList[i].sectionProperties.data.id);
if (matchingThread && (this.sectionProperties.commentList[i].sectionProperties.partIndex === this.sectionProperties.docLayer._selectedPart || app.file.fileBasedView)) {
rootComment = {
id: 'comment' + this.sectionProperties.commentList[i].sectionProperties.data.id,
enable: true,
data: this.sectionProperties.commentList[i].sectionProperties.data,
type: threadOnly ? 'comment' : 'rootcomment',
text: this.sectionProperties.commentList[i].sectionProperties.data.text,
annotation: this.sectionProperties.commentList[i],
children: []
};
menuStructure['children'].push(rootComment);
}
}
}
public createCommentStructureCalc (menuStructure: any, threadOnly: any): void {
var rootComment;
var commentList = this.sectionProperties.commentList;
var selectedTab = this.sectionProperties.docLayer._selectedPart;
for (var i: number = 0; i < commentList.length; i++) {
var matchingThread = !threadOnly || (threadOnly && threadOnly.sectionProperties.data.id === commentList[i].sectionProperties.data.id);
if (parseInt(commentList[i].sectionProperties.data.tab) === selectedTab && matchingThread) {
rootComment = {
id: 'comment' + commentList[i].sectionProperties.data.id,
enable: true,
data: commentList[i].sectionProperties.data,
type: threadOnly ? 'comment' : 'rootcomment',
text: commentList[i].sectionProperties.data.text,
annotation: commentList[i],
children: []
};
menuStructure['children'].push(rootComment);
}
}
}
// threadOnly - takes annotation indicating which thread will be generated
public createCommentStructure (menuStructure: any, threadOnly: any): void {
if (this.sectionProperties.docLayer._docType === 'text') {
this.createCommentStructureWriter(menuStructure, threadOnly);
}
else if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') {
this.createCommentStructureImpress(menuStructure, threadOnly);
}
else if (this.sectionProperties.docLayer._docType === 'spreadsheet') {
this.createCommentStructureCalc(menuStructure, threadOnly);
}
}
public newAnnotationMobile (comment: any, addCommentFn: any, isMod: any): void {
var commentData = comment.sectionProperties.data;
var callback = function(data: string) {
if (data) {
var annotation = comment;
annotation.sectionProperties.data.text = data;
comment.text = data;
addCommentFn.call(annotation, annotation, comment);
if (!isMod)
this.containerObject.removeSection(annotation);
}
else {
this.cancel(comment);
}
}.bind(this);
var id = 'new-annotation-dialog';
var dialogId = this.map.uiManager.generateModalId(id);
var json = this.map.uiManager._modalDialogJSON(id, '', true, [
{
id: 'input-modal-input',
type: 'multilineedit',
text: (commentData.text && isMod ? commentData.text: '')
},
{
id: '',
type: 'buttonbox',
text: '',
enabled: true,
children: [
{
id: 'response-cancel',
type: 'pushbutton',
text: _('Cancel'),
},
{
id: 'response-ok',
type: 'pushbutton',
text: _('Save'),
'has_default': true,
}
],
vertical: false,
layoutstyle: 'end'
},
]);
var cancelFunction = function() {
this.cancel(comment);
this.map.uiManager.closeModal(dialogId);
}.bind(this);
this.map.uiManager.showModal(json, [
{id: 'response-ok', func: function() {
if (typeof callback === 'function') {
var input = document.getElementById('input-modal-input') as HTMLTextAreaElement;
callback(input.value);
}
this.map.uiManager.closeModal(dialogId);
}.bind(this)},
{id: 'response-cancel', func: cancelFunction},
{id: '__POPOVER__', func: cancelFunction},
{id: '__DIALOG__', func: cancelFunction}
]);
var tagTd = 'td',
empty = '',
tagDiv = 'div';
var author = L.DomUtil.create('table', 'cool-annotation-table');
var tbody = L.DomUtil.create('tbody', empty, author);
var tr = L.DomUtil.create('tr', empty, tbody);
var tdImg = L.DomUtil.create(tagTd, 'cool-annotation-img', tr);
var tdAuthor = L.DomUtil.create(tagTd, 'cool-annotation-author', tr);
var imgAuthor = L.DomUtil.create('img', 'avatar-img', tdImg);
var user = this.map.getViewId(commentData.author);
L.LOUtil.setUserImage(imgAuthor, this.map, user);
imgAuthor.setAttribute('width', 32);
imgAuthor.setAttribute('height', 32);
var authorAvatarImg = imgAuthor;
var contentAuthor = L.DomUtil.create(tagDiv, 'cool-annotation-content-author', tdAuthor);
var contentDate = L.DomUtil.create(tagDiv, 'cool-annotation-date', tdAuthor);
$(contentAuthor).text(commentData.author);
$(authorAvatarImg).attr('src', commentData.avatar);
if (user >= 0) {
var color = L.LOUtil.rgbToHex(this.map.getViewColor(user));
$(authorAvatarImg).css('border-color', color);
}
if (commentData.dateTime) {
var d = new Date(commentData.dateTime.replace(/,.*/, 'Z'));
var dateOptions = { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' };
$(contentDate).text(isNaN(d.getTime()) ? comment.dateTime: d.toLocaleDateString((<any>String).locale, <any>dateOptions));
}
var newAnnotationDialog = document.getElementById('new-annotation-dialog');
$(newAnnotationDialog).css('width', '100%');
var dialogInput = newAnnotationDialog.children[0];
$(dialogInput).css('height', '30vh');
var parent = newAnnotationDialog.parentElement;
parent.insertBefore(author, parent.childNodes[0]);
document.getElementById('input-modal-input').focus();
}
public highlightComment (comment: any): void {
this.removeHighlighters();
var commentList = this.sectionProperties.commentList;
var lastChild: any = this.getLastChildIndexOf(comment.sectionProperties.data.id);
while (true && lastChild >= 0) {
commentList[lastChild].highlight();
if (commentList[lastChild].isRootComment())
break;
lastChild = this.getIndexOf(commentList[lastChild].sectionProperties.data.parent);
}
}
public removeHighlighters (): void {
var commentList = this.sectionProperties.commentList;
for (var i: number = 0; i < commentList.length; i++) {
if (commentList[i].sectionProperties.isHighlighted) {
commentList[i].removeHighlight();
}
}
}
public removeItem (id: any): void {
var annotation;
for (var i = 0; i < this.sectionProperties.commentList.length; i++) {
annotation = this.sectionProperties.commentList[i];
if (annotation.sectionProperties.data.id === id) {
this.containerObject.removeSection(annotation.name);
this.sectionProperties.commentList.splice(i, 1);
this.updateIdIndexMap();
break;
}
}
this.checkSize();
}
public click (annotation: any): void {
this.select(annotation);
}
public save (annotation: any): void {
var comment;
if (annotation.sectionProperties.data.id === 'new') {
comment = {
Text: {
type: 'string',
value: annotation.sectionProperties.data.text
},
Author: {
type: 'string',
value: annotation.sectionProperties.data.author
}
};
if (app.file.fileBasedView) {
this.map.setPart(this.sectionProperties.docLayer._selectedPart, false);
this.map.sendUnoCommand('.uno:InsertAnnotation', comment, true /* force */);
this.map.setPart(0, false);
}
else {
this.map.sendUnoCommand('.uno:InsertAnnotation', comment, true /* force */);
}
// Object is later removed in onACKComment when newly inserted comment object is available
// It's to reduce the flicker when using comment autosave
if (!CommentSection.autoSavedComment)
this.removeItem(annotation.sectionProperties.data.id);
} else if (annotation.sectionProperties.data.trackchange) {
comment = {
ChangeTrackingId: {
type: 'long',
value: annotation.sectionProperties.data.index
},
Text: {
type: 'string',
value: annotation.sectionProperties.data.text
}
};
this.map.sendUnoCommand('.uno:CommentChangeTracking', comment, true /* force */);
} else {
comment = {
Id: {
type: 'string',
value: annotation.sectionProperties.data.id
},
Author: {
type: 'string',
value: annotation.sectionProperties.data.author
},
Text: {
type: 'string',
value: annotation.sectionProperties.data.text
}
};
this.map.sendUnoCommand('.uno:EditAnnotation', comment, true /* force */);
}
this.unselect();
this.map.focus();
}
public reply (annotation: any): void {
if (cool.Comment.isAnyEdit()) {
cool.CommentSection.showCommentEditingWarning();
return;
}
if ((<any>window).mode.isMobile()) {
var avatar = undefined;
var author = this.map.getViewName(this.sectionProperties.docLayer._viewId);
if (author in this.map._viewInfoByUserName) {
avatar = this.map._viewInfoByUserName[author].userextrainfo.avatar;
}
if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') {
this.newAnnotationMobile(annotation, annotation.onReplyClick, /* isMod */ false);
}
else {
var replyAnnotation = {
text: '',
textrange: '',
author: author,
dateTime: new Date().toDateString(),
id: annotation.sectionProperties.data.id,
avatar: avatar,
parent: annotation.sectionProperties.data.parent,
anchorPos: [annotation.sectionProperties.data.anchorPos[0], annotation.sectionProperties.data.anchorPos[1]],
};
var replyAnnotationSection = new cool.Comment(replyAnnotation, replyAnnotation.id === 'new' ? {noMenu: true} : {}, this);
replyAnnotationSection.name += '-reply';
this.newAnnotationMobile(replyAnnotationSection, annotation.onReplyClick, /* isMod */ false);
}
}
else {
annotation.reply();
this.select(annotation, true);
annotation.focus();
}
}
public modify (annotation: any): void {
if (cool.Comment.isAnyEdit()) {
cool.CommentSection.showCommentEditingWarning();
return;
}
if ((<any>window).mode.isMobile()) {
this.newAnnotationMobile(annotation, function(annotation: any) {
this.save(annotation);
}.bind(this), /* isMod */ true);
}
else {
// Make sure that comment is not transitioning and comment menu is not open.
var tempFunction = function() {
setTimeout(function() {
if (String(annotation.sectionProperties.container.dataset.transitioning) === 'true' || annotation.sectionProperties.contextMenu === true) {
tempFunction();
}
else {
annotation.edit();
this.select(annotation, true);
annotation.focus();
}
}.bind(this), 1);
}.bind(this);
tempFunction();
}
}
private showCollapsedReplies(rootIndex: number) {
if (!this.sectionProperties.commentList.length)
return;
var lastIndex = this.getLastChildIndexOf(this.sectionProperties.commentList[rootIndex].sectionProperties.data.id);
var rootComment = this.sectionProperties.commentList[rootIndex];
while (rootIndex <= lastIndex) {
this.sectionProperties.commentList[rootIndex].sectionProperties.container.style.display = '';
this.sectionProperties.commentList[rootIndex].sectionProperties.container.style.visibility = '';
rootIndex++;
}
rootComment.updateThreadInfoIndicator();
}
private collapseReplies(rootIndex: number, rootId: number) {
var lastChild = this.getLastChildIndexOf(rootId);
for (var i = lastChild; i > rootIndex; i--) {
this.sectionProperties.commentList[i].sectionProperties.container.style.display = 'none';
}
this.sectionProperties.commentList[i].updateThreadInfoIndicator();
}
private cssToCorePixels(cssPixels: number) {
return cssPixels * app.dpiScale;
}
public select (annotation: Comment, force: boolean = false): void {
if (force || (annotation && !annotation.pendingInit && annotation !== this.sectionProperties.selectedComment)) {
// Select the root comment
var idx = this.getRootIndexOf(annotation.sectionProperties.data.id);
// no need to reselect comment, it will cuase to scroll to root comment unnecessarily
if (this.sectionProperties.selectedComment === this.sectionProperties.commentList[idx]) {
this.update();
return;
}
// Unselect first if there anything selected
if (this.sectionProperties.selectedComment)
this.unselect();
this.sectionProperties.selectedComment = this.sectionProperties.commentList[idx];
if (this.sectionProperties.selectedComment && !$(this.sectionProperties.selectedComment.sectionProperties.container).hasClass('annotation-active')) {
$(this.sectionProperties.selectedComment.sectionProperties.container).addClass('annotation-active');
}
const selectedComment = this.sectionProperties.selectedComment;
const docType = this.sectionProperties.docLayer._docType;
let position: Array<number> = null;
switch (docType) {
case 'text':
{
position = this.numberArrayToCorePixFromTwips(
selectedComment.sectionProperties.data.anchorPos, 0, 2);
break;
}
case 'spreadsheet':
{
// in calc comments are not visible on canvas, anchor vertical position is always 1
// position is already in core pixels
position = selectedComment.getPosition();
break;
}
default:
break;
}
if (position) {
const rect = selectedComment.sectionProperties.container.getBoundingClientRect();
const annotationTop = position[1];
const annotationHeight = this.cssToCorePixels(rect.height);
const annotationBottom = position[1] + annotationHeight;
if (!this.isInViewPort([annotationTop, annotationBottom]) && position[1] !== 0 && annotation === selectedComment) {
console.debug('Annotation outside view - scroll');
const scrollSection = app.sectionContainer.getSectionWithName(L.CSections.Scroll.name);
const screenTopBottom = this.getScreenTopBottom();
scrollSection.scrollVerticalWithOffset(
position[1] < 0 ? 0 : position[1] - screenTopBottom[1] + annotationHeight);
if (docType === 'spreadsheet' && selectedComment) {
selectedComment.positionCalcComment();
selectedComment.focus();
}
}
}
if (this.isCollapsed) {
this.showCollapsedReplies(idx);
selectedComment.updateThreadInfoIndicator();
}
this.update();
}
}
/// returns canvas top and bottom position in core pixels
public getScreenTopBottom(): Array<number> {
const scrollSection = app.sectionContainer.getSectionWithName(L.CSections.Scroll.name);
const screenTop = scrollSection.containerObject.getDocumentTopLeft()[1];
const screenBottom = screenTop + this.cssToCorePixels($('#map').height());
return [screenTop, screenBottom];
}
/// checks if vertical top and bottom point (in core pixels) is shown on the screen currently
private isInViewPort(positionTopBotton: Array<number>): boolean {
const screenTopBottom = this.getScreenTopBottom();
const top = positionTopBotton[0];
const bottom = positionTopBotton[1];
return (
screenTopBottom[0] <= top &&
screenTopBottom[1] >= bottom
);
}
public unselect (): void {
if (this.sectionProperties.selectedComment && this.sectionProperties.selectedComment.sectionProperties.data.id != 'new') {
if (this.sectionProperties.selectedComment && $(this.sectionProperties.selectedComment.sectionProperties.container).hasClass('annotation-active'))
$(this.sectionProperties.selectedComment.sectionProperties.container).removeClass('annotation-active');
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.sectionProperties.selectedComment.hide();
if (this.sectionProperties.commentsAreListed && this.isCollapsed) {
this.sectionProperties.selectedComment.setCollapsed();
this.collapseReplies(this.getRootIndexOf(this.sectionProperties.selectedComment.sectionProperties.data.id), this.sectionProperties.selectedComment.sectionProperties.data.id);
}
this.sectionProperties.selectedComment = null;
this.update();
}
}
public saveReply (annotation: any): void {
var comment = {
Id: {
type: 'string',
value: annotation.sectionProperties.data.id
},
Text: {
type: 'string',
value: annotation.sectionProperties.data.reply
}
};
if (this.sectionProperties.docLayer._docType === 'text' || this.sectionProperties.docLayer._docType === 'spreadsheet')
this.map.sendUnoCommand('.uno:ReplyComment', comment);
else if (this.sectionProperties.docLayer._docType === 'presentation')
this.map.sendUnoCommand('.uno:ReplyToAnnotation', comment);
this.unselect();
this.map.focus();
}
public cancel (annotation: any): void {
if (annotation.sectionProperties.data.id === 'new') {
this.removeItem(annotation.sectionProperties.data.id);
}
if (this.sectionProperties.selectedComment === annotation) {
this.unselect();
} else {
this.update();
}
this.map.focus();
}
public onRedlineAccept (e: any): void {
var command = {
AcceptTrackedChange: {
type: 'unsigned short',
value: e.id.substring('change-'.length)
}
};
this.map.sendUnoCommand('.uno:AcceptTrackedChange', command);
this.unselect();
this.map.focus();
}
public onRedlineReject (e: any): void {
var command = {
RejectTrackedChange: {
type: 'unsigned short',
value: e.id.substring('change-'.length)
}
};
this.map.sendUnoCommand('.uno:RejectTrackedChange', command);
this.unselect();
this.map.focus();
}
public remove (id: any): void {
var comment = {
Id: {
type: 'string',
value: id
}
};
if (app.file.fileBasedView) // We have to set the part from which the comment will be removed as selected part before the process.
this.map.setPart(this.sectionProperties.docLayer._selectedPart, false);
if (this.sectionProperties.docLayer._docType === 'text')
this.map.sendUnoCommand('.uno:DeleteComment', comment);
else if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing')
this.map.sendUnoCommand('.uno:DeleteAnnotation', comment);
else if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.map.sendUnoCommand('.uno:DeleteNote', comment);
if (app.file.fileBasedView)
this.map.setPart(0, false);
this.unselect();
this.map.focus();
}
public removeThread (id: any): void {
var comment = {
Id: {
type: 'string',
value: id
}
};
this.map.sendUnoCommand('.uno:DeleteCommentThread', comment);
this.unselect();
this.map.focus();
}
public resolve (annotation: any): void {
var comment = {
Id: {
type: 'string',
value: annotation.sectionProperties.data.id
}
};
this.map.sendUnoCommand('.uno:ResolveComment', comment);
}
public resolveThread (annotation: any): void {
var comment = {
Id: {
type: 'string',
value: annotation.sectionProperties.data.id
}
};
this.map.sendUnoCommand('.uno:ResolveCommentThread', comment);
}
public getIndexOf (id: any): number {
const index = this.idIndexMap.get(id);
return (index === undefined) ? -1 : index;
}
public isThreadResolved (annotation: any): boolean {
// If comment has children.
if (annotation.sectionProperties.children.length > 0) {
for (var i = 0; i < annotation.sectionProperties.children.length; i++) {
if (annotation.sectionProperties.children[i].sectionProperties.data.resolved !== 'true')
return false;
}
return true;
}
// If it has a parent.
else if (annotation.sectionProperties.data.parent !== '0') {
var index = this.getSubRootIndexOf(annotation.sectionProperties.data.parent);
var comment = this.sectionProperties.commentList[index];
if (comment.sectionProperties.data.resolved !== 'true')
return false;
else if (comment.sectionProperties.children.length > 0) {
for (var i = 0; i < comment.sectionProperties.children.length; i++) {
if (comment.sectionProperties.children[i].sectionProperties.data.resolved !== 'true')
return false;
}
return true;
}
}
}
private initializeContextMenus (): void {
var docLayer = this.sectionProperties.docLayer;
L.installContextMenu({
selector: '.cool-annotation-menu',
trigger: 'none',
className: 'cool-font',
build: function ($trigger: any) {
return {
autoHide: true,
items: {
modify: {
name: _('Modify'),
callback: function (key: any, options: any) {
this.modify.call(this, options.$trigger[0].annotation);
}.bind(this)
},
reply: (docLayer._docType !== 'text' && docLayer._docType !== 'presentation') ? undefined : {
name: _('Reply'),
callback: function (key: any, options: any) {
this.reply.call(this, options.$trigger[0].annotation);
}.bind(this)
},
remove: {
name: _('Remove'),
callback: function (key: any, options: any) {
this.remove.call(this, options.$trigger[0].annotation.sectionProperties.data.id);
}.bind(this)
},
removeThread: docLayer._docType !== 'text' || $trigger[0].isRoot === true ? undefined : {
name: _('Remove Thread'),
callback: function (key: any, options: any) {
this.removeThread.call(this, options.$trigger[0].annotation.sectionProperties.data.id);
}.bind(this)
},
resolve: docLayer._docType !== 'text' ? undefined : {
name: $trigger[0].annotation.sectionProperties.data.resolved === 'false' ? _('Resolve') : _('Unresolve'),
callback: function (key: any, options: any) {
this.resolve.call(this, options.$trigger[0].annotation);
}.bind(this)
},
resolveThread: docLayer._docType !== 'text' || $trigger[0].isRoot === true ? undefined : {
name: this.isThreadResolved($trigger[0].annotation) ? _('Unresolve Thread') : _('Resolve Thread'),
callback: function (key: any, options: any) {
this.resolveThread.call(this, options.$trigger[0].annotation);
}.bind(this)
}
},
};
}.bind(this),
events: {
show: function (options: any) {
options.$trigger[0].annotation.sectionProperties.contextMenu = true;
setTimeout(function() {
options.items.modify.$node[0].tabIndex = 0;
options.items.modify.$node[0].focus();
}.bind(this), 10);
},
hide: function (options: any) {
options.$trigger[0].annotation.sectionProperties.contextMenu = false;
}
}
});
L.installContextMenu({
selector: '.cool-annotation-menu-redline',
trigger: 'none',
className: 'cool-font',
items: {
modify: {
name: _('Comment'),
callback: function (key: any, options: any) {
this.modify.call(this, options.$trigger[0].annotation);
}.bind(this)
}
},
events: {
show: function (options: any) {
options.$trigger[0].annotation.sectionProperties.contextMenu = true;
},
hide: function (options: any) {
options.$trigger[0].annotation.sectionProperties.contextMenu = false;
}
}
});
}
public onResize (): void {
this.checkCollapseState();
this.update();
// When window is resized, it may mean that comment wizard is closed. So we hide the highlights.
this.removeHighlighters();
this.containerObject.requestReDraw();
}
public onNewDocumentTopLeft (): void {
if (this.sectionProperties.docLayer._docType === 'spreadsheet') {
if (this.sectionProperties.selectedComment)
this.sectionProperties.selectedComment.hide();
}
this.update();
}
private showHideComments (): void {
for (var i: number = 0; i < this.sectionProperties.commentList.length; i++) {
this.showHideComment(this.sectionProperties.commentList[i]);
}
}
public showHideComment (annotation: any): void {
// This manually shows/hides comments
if (!this.sectionProperties.showResolved && this.sectionProperties.docLayer._docType === 'text') {
if (annotation.isContainerVisible() && annotation.sectionProperties.data.resolved === 'true') {
if (this.sectionProperties.selectedComment == annotation) {
this.unselect();
}
annotation.hide();
annotation.update();
} else if (!annotation.isContainerVisible() && annotation.sectionProperties.data.resolved === 'false') {
annotation.show();
annotation.update();
}
this.update();
}
else if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') {
if (annotation.sectionProperties.partIndex === this.sectionProperties.docLayer._selectedPart || app.file.fileBasedView) {
if (!annotation.isContainerVisible()) {
annotation.show();
annotation.update();
this.update();
}
}
else {
annotation.hide();
annotation.update();
this.update();
}
}
}
public add (comment: any): cool.Comment {
var annotation = new cool.Comment(comment, comment.id === 'new' ? {noMenu: true} : {}, this);
/*
Remove if a comment with the same id exists.
When user deletes a parent and a child of that parent and undoes the operation respectively:
* The first undo: Core side sends the deleted child - this is fine.
* The second undo: Core side sends parent and child together - which is not fine. We already had the child with the first undo command.
So, delete if a comment already exists and trust core side about the ids of the comments.
*/
if (this.containerObject.doesSectionExist(annotation.name))
this.removeItem(annotation.name);
this.containerObject.addSection(annotation);
this.sectionProperties.commentList.push(annotation);
this.adjustParentAdd(annotation);
this.orderCommentList(); // Also updates the index map.
this.checkSize();
if (this.isCollapsed && comment.id !== 'new')
annotation.setCollapsed();
else
annotation.setExpanded();
// check if we are the author
// then select it so it does not get lost in a long list of comments and replies.
const authorName = this.map.getViewName(this.sectionProperties.docLayer._viewId);
const newComment = annotation.sectionProperties.data.id === 'new';
if (!newComment && (authorName === annotation.sectionProperties.data.author)) {
this.select(annotation);
}
return annotation;
}
public adjustRedLine (redline: any): boolean {
// All sane values ?
if (!redline.textRange) {
console.warn('Redline received has invalid textRange');
return false;
}
// transform change tracking index into an id
redline.id = 'change-' + redline.index;
redline.parent = '0'; // Redlines don't have parents, we need to specify this for consistency.
redline.anchorPos = this.stringToRectangles(redline.textRange)[0];
redline.anchorPix = this.numberArrayToCorePixFromTwips(redline.anchorPos, 0, 2);
redline.trackchange = true;
redline.text = redline.comment;
var rectangles = L.PolyUtil.rectanglesToPolygons(L.LOUtil.stringToRectangles(redline.textRange), this.sectionProperties.docLayer);
if (rectangles.length > 0) {
redline.textSelected = L.polygon(rectangles, {
pointerEvents: 'all',
interactive: false,
fillOpacity: 0,
opacity: 0
});
redline.textSelected.addEventParent(this.map);
redline.textSelected.on('click', function() {
this.selectById(redline.id);
}, this);
}
return true;
}
public getComment (id: any): any {
const index = this.getIndexOf(id);
return index == -1 ? null : this.sectionProperties.commentList[index];
}
private checkIfCommentHasPreAssignedChildren(comment: CommentSection) {
for (var i = 0; i < this.sectionProperties.commentList.length; i++) {
var possibleChild: Comment = this.sectionProperties.commentList[i];
if (possibleChild.sectionProperties.possibleParentCommentId !== null) {
if (possibleChild.sectionProperties.possibleParentCommentId === comment.sectionProperties.data.id) {
if (!comment.sectionProperties.children.includes(possibleChild))
comment.sectionProperties.children.push(possibleChild);
}
}
}
}
// Adjust parent-child relationship, if required, after `comment` is added
public adjustParentAdd (comment: any): void {
if (comment.sectionProperties.data.parent === undefined)
comment.sectionProperties.data.parent = '0';
if (comment.sectionProperties.data.parent !== '0') {
var parentIdx = this.getIndexOf(comment.sectionProperties.data.parent);
if (parentIdx === -1) {
console.warn('adjustParentAdd: No parent comment to attach received comment to. ' +
'Parent comment ID sought is :' + comment.sectionProperties.data.parent + ' for current comment with ID : ' + comment.sectionProperties.data.id);
comment.sectionProperties.possibleParentCommentId = comment.sectionProperties.data.parent; // Save the proposed parentId so we can remember if such parent appears.
comment.setAsRootComment(); // Set this to default since there is no such parent at the moment.
}
else {
var parentComment = this.sectionProperties.commentList[parentIdx];
if (parentComment && !parentComment.sectionProperties.children.includes(comment))
parentComment.sectionProperties.children.push(comment);
}
}
// Check if any of the child comments targets the newly added comment as parent.
this.checkIfCommentHasPreAssignedChildren(comment);
}
// Adjust parent-child relationship, if required, after `comment` is removed
public adjustParentRemove (comment: any): void {
var parentIdx = this.getIndexOf(comment.getParentCommentId());
// If a child comment is removed.
var parentComment = this.sectionProperties.commentList[parentIdx];
if (parentComment) {
var index = parentComment.getIndexOfChild(comment);
if (index >= 0)
parentComment.removeChildByIndex(index); // Removed comment has a parent. Remove the comment also from its parent's list.
}
// If a parent comment is removed.
for (var i = 0; i < comment.getChildrenLength(); i++) { // Loop over removed comment's children.
var childComment = comment.getChildByIndex(i);
if (childComment)
childComment.setAsRootComment(); // The children have no parent comment any more.
}
}
public onACKComment (obj: any): void {
var id;
var changetrack = obj.redline ? true : false;
var dataroot = changetrack ? 'redline' : 'comment';
if (changetrack) {
obj.redline.id = 'change-' + obj.redline.index;
}
var action = changetrack ? obj.redline.action : obj.comment.action;
if (!changetrack && obj.comment.parent === undefined) {
if (obj.comment.parentId)
obj.comment.parent = String(obj.comment.parentId);
else
obj.comment.parent = '0';
}
if (changetrack && obj.redline.author in this.map._viewInfoByUserName) {
obj.redline.avatar = this.map._viewInfoByUserName[obj.redline.author].userextrainfo.avatar;
}
else if (!changetrack && obj.comment.author in this.map._viewInfoByUserName) {
obj.comment.avatar = this.map._viewInfoByUserName[obj.comment.author].userextrainfo.avatar;
}
if ((<any>window).mode.isMobile()) {
var annotation = this.sectionProperties.commentList[this.getRootIndexOf(obj[dataroot].id)];
if (!annotation)
annotation = this.sectionProperties.commentList[this.getRootIndexOf(obj[dataroot].parent)]; //this is required for reload after reply in writer
}
if (action === 'Add') {
if (changetrack) {
if (!this.adjustRedLine(obj.redline)) {
// something wrong in this redline
return;
}
this.add(obj.redline);
} else {
const currentComment = this.getComment(obj[dataroot].id);
if (currentComment !== null) {
if (obj[dataroot].layoutStatus !== undefined) {
currentComment.sectionProperties.data.layoutStatus = parseInt(obj[dataroot].layoutStatus);
currentComment.setLayoutClass();
}
return;
}
this.adjustComment(obj.comment);
annotation = this.add(obj.comment);
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
annotation.hide();
var autoSavedComment = CommentSection.autoSavedComment;
if (autoSavedComment) {
var isOurComment = annotation.isAutoSaved();
if (isOurComment) {
annotation.sectionProperties.container.style.visibility = 'visible';
annotation.sectionProperties.autoSave.innerText = _('Autosaved');
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
annotation.show();
annotation.edit();
if (autoSavedComment.sectionProperties.data.id === 'new')
this.removeItem(autoSavedComment.sectionProperties.data.id);
CommentSection.autoSavedComment = null;
CommentSection.commentWasAutoAdded = true;
}
}
}
if (this.sectionProperties.selectedComment && !this.sectionProperties.selectedComment.isEdit()) {
this.map.focus();
}
} else if (action === 'Remove') {
id = obj[dataroot].id;
var removed = this.getComment(id);
if (removed) {
this.adjustParentRemove(removed);
if (this.sectionProperties.selectedComment === removed) {
this.unselect();
this.removeItem(id);
}
else {
this.removeItem(id);
this.update();
}
}
} else if (action === 'RedlinedDeletion') {
id = obj[dataroot].id;
var _redlined = this.getComment(id);
if (_redlined && _redlined.sectionProperties.data.layoutStatus === CommentLayoutStatus.INSERTED) {
// Do normal removal if comment was added while recording was on
// No need to keep the deleted comment
obj[dataroot].action = 'Remove';
this.onACKComment(obj);
return;
}
if (_redlined) {
_redlined.sectionProperties.data.layoutStatus = CommentLayoutStatus.DELETED;
_redlined.setLayoutClass();
}
} else if (action === 'Modify') {
id = obj[dataroot].id;
var modified = this.getComment(id);
if (modified) {
var modifiedObj;
if (changetrack) {
if (!this.adjustRedLine(obj.redline)) {
// something wrong in this redline
return;
}
modifiedObj = obj.redline;
} else {
this.adjustComment(obj.comment);
modifiedObj = obj.comment;
}
modified.setData(modifiedObj);
modified.update();
this.update();
if (CommentSection.autoSavedComment) {
CommentSection.autoSavedComment.sectionProperties.autoSave.innerText = _('Autosaved');
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
modified.show();
modified.edit();
if(this.shouldCollapse())
modified.setCollapsed();
}
}
} else if (action === 'Resolve') {
id = obj[dataroot].id;
var resolved = this.getComment(id);
if (resolved) {
var resolvedObj;
if (changetrack) {
if (!this.adjustRedLine(obj.redline)) {
// something wrong in this redline
return;
}
resolvedObj = obj.redline;
} else {
this.adjustComment(obj.comment);
resolvedObj = obj.comment;
}
resolved.setData(resolvedObj);
resolved.update();
this.showHideComment(resolved);
this.update();
}
}
if ((<any>window).mode.isMobile()) {
var shouldOpenWizard = false;
var wePerformedAction = obj.comment.author === this.map.getViewName(this.sectionProperties.docLayer._viewId);
if ((<any>window).commentWizard || (action === 'Add' && wePerformedAction))
shouldOpenWizard = true;
if (shouldOpenWizard) {
this.sectionProperties.docLayer._openCommentWizard(annotation);
}
}
if (this.sectionProperties.docLayer._docType === 'text')
this.updateThreadInfoIndicator();
}
public selectById (commentId: any): void {
var idx = this.getRootIndexOf(commentId);
var annotation = this.sectionProperties.commentList[idx];
this.select(annotation);
}
public stringToRectangles (str: string): number[][] {
var strString = typeof str !== 'string' ? String(str) : str;
var matches = strString.match(/\d+/g);
var rectangles: number[][] = [];
if (matches !== null) {
for (var i: number = 0; i < matches.length; i += 4) {
rectangles.push([parseInt(matches[i]), parseInt(matches[i + 1]), parseInt(matches[i + 2]), parseInt(matches[i + 3])]);
}
}
return rectangles;
}
public onPartChange (): void {
for (var i: number = 0; i < this.sectionProperties.commentList.length; i++) {
this.showHideComment(this.sectionProperties.commentList[i]);
}
if (this.sectionProperties.selectedComment)
this.sectionProperties.selectedComment.onCancelClick(null);
this.checkSize();
}
// This converts the specified number of values into core pixels from twips.
// Returns a new array with the length of specified numbers.
private numberArrayToCorePixFromTwips (numberArray: Array<number>, startIndex: number = 0, length: number = null): Array<number> {
if (!length)
length = numberArray.length;
if (startIndex < 0)
startIndex = 0;
if (length < 0)
length = 0;
if (startIndex + length > numberArray.length)
length = numberArray.length - startIndex;
var result = new Array(length);
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
for (var i = startIndex; i < length; i++) {
result[i] = Math.round(numberArray[i] * ratio);
}
return result;
}
// In file based view, we need to move comments to their part's position.
// Because all parts are drawn on the screen. Core side doesn't have this feature.
// Core side sends the information in part coordinates.
// When a coordinate like [0, 0] is inside 2nd part for example, that coordinate should correspond to a value like (just guessing) [0, 45646].
// See that y value is different. Because there is 1st part above the 2nd one in the view.
// We will add their part's position to comment's variables.
// When we are saving their position, we will remove the additions before sending the information.
private adjustCommentFileBasedView (comment: any): void {
// Below calculations are the same with the ones we do while drawing tiles in fileBasedView.
var partHeightTwips = this.sectionProperties.docLayer._partHeightTwips + this.sectionProperties.docLayer._spaceBetweenParts;
var index = this.sectionProperties.docLayer._partHashes.indexOf(String(comment.parthash));
var yAddition = index * partHeightTwips;
comment.yAddition = yAddition; // We'll use this while we save the new position of the comment.
comment.trackchange = false;
comment.rectangles = this.stringToRectangles(comment.textRange || comment.anchorPos || comment.rectangle); // Simple array of point arrays [x1, y1, x2, y2].
comment.rectangles[0][1] += yAddition; // There is only one rectangle for our case.
comment.rectanglesOriginal = this.stringToRectangles(comment.textRange || comment.anchorPos || comment.rectangle); // This unmodified version will be kept for re-calculations.
comment.rectanglesOriginal[0][1] += yAddition;
comment.anchorPos = this.stringToRectangles(comment.anchorPos || comment.rectangle)[0];
comment.anchorPos[1] += yAddition;
if (comment.rectangle) {
comment.rectangle = this.stringToRectangles(comment.rectangle)[0]; // This is the position of the marker.
comment.rectangle[1] += yAddition;
}
comment.anchorPix = this.numberArrayToCorePixFromTwips(comment.anchorPos, 0, 2);
comment.parthash = comment.parthash ? comment.parthash: null;
var viewId = this.map.getViewId(comment.author);
var color = viewId >= 0 ? L.LOUtil.rgbToHex(this.map.getViewColor(viewId)) : '#43ACE8';
comment.color = color;
}
// Normally, a comment's position information is the same with the desktop version.
// So we can use it directly.
private adjustCommentNormal (comment: any): void {
comment.trackchange = false;
if (comment.cellRange) {
// turn cell range string into cell bounds
comment.cellRange = this.sectionProperties.docLayer._parseCellRange(comment.cellRange);
}
var cellPos = comment.cellRange ? this.sectionProperties.docLayer._cellRangeToTwipRect(comment.cellRange).toRectangle() : null;
comment.rectangles = this.stringToRectangles(comment.textRange || comment.anchorPos || comment.rectangle || cellPos); // Simple array of point arrays [x1, y1, x2, y2].
comment.rectanglesOriginal = this.stringToRectangles(comment.textRange || comment.anchorPos || comment.rectangle || cellPos); // This unmodified version will be kept for re-calculations.
comment.anchorPos = this.stringToRectangles(comment.anchorPos || comment.rectangle || cellPos)[0];
comment.anchorPix = this.numberArrayToCorePixFromTwips(comment.anchorPos, 0, 2);
comment.parthash = comment.parthash ? comment.parthash: null;
comment.tab = (comment.tab || comment.tab === 0) ? comment.tab: null;
comment.layoutStatus = comment.layoutStatus !== undefined ? parseInt(comment.layoutStatus): null;
if (comment.parentId)
comment.parent = String(comment.parentId);
if (comment.rectangle) {
comment.rectangle = this.stringToRectangles(comment.rectangle)[0]; // This is the position of the marker (Impress & Draw).
}
var viewId = this.map.getViewId(comment.author);
var color = viewId >= 0 ? L.LOUtil.rgbToHex(this.map.getViewColor(viewId)) : '#43ACE8';
comment.color = color;
}
private adjustComment (comment: any): void {
if (!app.file.fileBasedView)
this.adjustCommentNormal(comment);
else
this.adjustCommentFileBasedView(comment);
}
// Returns the last comment id of comment thread containing the given id
private getLastChildIndexOf (id: any): number {
var index = this.getIndexOf(id);
index = this.getRootIndexOf(this.sectionProperties.commentList[index].sectionProperties.data.id);
while
(
this.sectionProperties.commentList[index + 1] &&
index + 1 < this.sectionProperties.commentList.length &&
this.sectionProperties.commentList[index + 1].sectionProperties.data.parent !== '0'
) {
index++;
}
return index;
}
// If the file type is presentation or drawing then we shall check the selected part in order to hide comments from other parts.
// But if file is in fileBasedView, then we will not hide any comments from not-selected/viewed parts.
private mustCheckSelectedPart (): boolean {
return (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') && !app.file.fileBasedView;
}
private layoutUp (subList: any, actualPosition: Array<number>, lastY: number): number {
var height: number;
for (var i = 0; i < subList.length; i++) {
height = subList[i].sectionProperties.container.getBoundingClientRect().height;
lastY = subList[i].sectionProperties.data.anchorPix[1] + height < lastY ? subList[i].sectionProperties.data.anchorPix[1]: lastY - (height * app.dpiScale);
(new L.PosAnimation()).run(subList[i].sectionProperties.container, {x: Math.round(actualPosition[0] / app.dpiScale), y: Math.round(lastY / app.dpiScale)});
if (!subList[i].isEdit())
subList[i].show();
}
return lastY;
}
private loopUp (startIndex: number, x: number, startY: number): number {
var tmpIdx = 0;
var checkSelectedPart: boolean = this.mustCheckSelectedPart();
startY -= this.sectionProperties.marginY;
// Pass over all comments present
for (var i = startIndex; i > -1;) {
var subList = [];
tmpIdx = i;
do {
this.sectionProperties.commentList[tmpIdx].sectionProperties.data.anchorPix = this.numberArrayToCorePixFromTwips(this.sectionProperties.commentList[tmpIdx].sectionProperties.data.anchorPos, 0, 2);
this.sectionProperties.commentList[tmpIdx].sectionProperties.data.anchorPix[1] -= this.documentTopLeft[1];
// Add this item to the list of comments.
if (this.sectionProperties.commentList[tmpIdx].sectionProperties.data.resolved !== 'true' || this.sectionProperties.showResolved) {
if (!checkSelectedPart || this.sectionProperties.docLayer._selectedPart === this.sectionProperties.commentList[tmpIdx].sectionProperties.partIndex)
subList.push(this.sectionProperties.commentList[tmpIdx]);
}
tmpIdx = tmpIdx - 1;
// Continue this loop, until we reach the last item, or an item which is not a direct descendant of the previous item.
} while (tmpIdx > -1 && this.sectionProperties.commentList[tmpIdx].sectionProperties.data.parent === this.sectionProperties.commentList[tmpIdx + 1].sectionProperties.data.id);
if (subList.length > 0) {
startY = this.layoutUp(subList, [x, subList[0].sectionProperties.data.anchorPix[1]], startY);
i = i - subList.length;
} else {
i = tmpIdx;
}
startY -= this.sectionProperties.marginY;
}
return startY;
}
private layoutDown (subList: any, actualPosition: Array<number>, lastY: number): number {
var selectedComment = subList[0] === this.sectionProperties.selectedComment;
for (var i = 0; i < subList.length; i++) {
lastY = subList[i].sectionProperties.data.anchorPix[1] > lastY ? subList[i].sectionProperties.data.anchorPix[1]: lastY;
var isRTL = document.documentElement.dir === 'rtl';
if (selectedComment)
(new L.PosAnimation()).run(subList[i].sectionProperties.container, {x: Math.round(actualPosition[0] / app.dpiScale) - this.sectionProperties.deflectionOfSelectedComment * (isRTL ? -1 : 1), y: Math.round(lastY / app.dpiScale)});
else
(new L.PosAnimation()).run(subList[i].sectionProperties.container, {x: Math.round(actualPosition[0] / app.dpiScale), y: Math.round(lastY / app.dpiScale)});
lastY += (subList[i].sectionProperties.container.getBoundingClientRect().height * app.dpiScale);
if (!subList[i].isEdit())
subList[i].show();
}
return lastY;
}
private loopDown (startIndex: number, x: number, startY: number): number {
var tmpIdx = 0;
var checkSelectedPart: boolean = this.mustCheckSelectedPart();
// Pass over all comments present
for (var i = startIndex; i < this.sectionProperties.commentList.length;) {
var subList = [];
tmpIdx = i;
do {
this.sectionProperties.commentList[tmpIdx].sectionProperties.data.anchorPix = this.numberArrayToCorePixFromTwips(this.sectionProperties.commentList[tmpIdx].sectionProperties.data.anchorPos, 0, 2);
this.sectionProperties.commentList[tmpIdx].sectionProperties.data.anchorPix[1] -= this.documentTopLeft[1];
// Add this item to the list of comments.
if (this.sectionProperties.commentList[tmpIdx].sectionProperties.data.resolved !== 'true' || this.sectionProperties.showResolved) {
if (!checkSelectedPart || this.sectionProperties.docLayer._selectedPart === this.sectionProperties.commentList[tmpIdx].sectionProperties.partIndex)
subList.push(this.sectionProperties.commentList[tmpIdx]);
}
tmpIdx = tmpIdx + 1;
// Continue this loop, until we reach the last item, or an item which is not a direct descendant of the previous item.
} while (tmpIdx < this.sectionProperties.commentList.length && this.sectionProperties.commentList[tmpIdx].sectionProperties.data.parent !== '0');
if (subList.length > 0) {
startY = this.layoutDown(subList, [x, subList[0].sectionProperties.data.anchorPix[1]], startY);
i = i + subList.length;
} else {
i = tmpIdx;
}
startY += this.sectionProperties.marginY;
}
return startY;
}
public hideArrow (): void {
if (this.sectionProperties.arrow) {
document.getElementById('document-container').removeChild(this.sectionProperties.arrow);
this.sectionProperties.arrow = null;
}
}
private showArrow (startPoint: Array<number>, endPoint: Array<number>): void {
var anchorSection = this.containerObject.getDocumentAnchorSection();
startPoint[0] -= anchorSection.myTopLeft[0] + this.documentTopLeft[0];
startPoint[1] -= anchorSection.myTopLeft[1] + this.documentTopLeft[1];
endPoint[1] -= anchorSection.myTopLeft[1] + this.documentTopLeft[1];
startPoint[0] = Math.floor(startPoint[0] / app.dpiScale);
startPoint[1] = Math.floor(startPoint[1] / app.dpiScale);
endPoint[0] = Math.floor(endPoint[0] / app.dpiScale);
endPoint[1] = Math.floor(endPoint[1] / app.dpiScale);
if (this.sectionProperties.arrow !== null) {
var line: SVGLineElement = <SVGLineElement>(<any>document.getElementById('comment-arrow-line'));
line.setAttribute('x1', String(startPoint[0]));
line.setAttribute('y1', String(startPoint[1]));
line.setAttribute('x2', String(endPoint[0]));
line.setAttribute('y2', String(endPoint[1]));
}
else {
var svg: SVGElement = (<any>document.createElementNS('http://www.w3.org/2000/svg', 'svg'));
svg.setAttribute('version', '1.1');
svg.style.zIndex = '9';
svg.id = 'comment-arrow-container';
svg.style.position = 'absolute';
svg.style.top = svg.style.left = svg.style.right = svg.style.bottom = '0';
svg.setAttribute('width', String(this.context.canvas.width));
svg.setAttribute('height', String(this.context.canvas.height));
var line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.id = 'comment-arrow-line';
line.setAttribute('x1', String(startPoint[0]));
line.setAttribute('y1', String(startPoint[1]));
line.setAttribute('x2', String(endPoint[0]));
line.setAttribute('y2', String(endPoint[1]));
line.setAttribute('stroke', 'darkblue');
line.setAttribute('stroke-width', '1');
svg.appendChild(line);
document.getElementById('document-container').appendChild(svg);
this.sectionProperties.arrow = svg;
}
}
private doLayout (): void {
if ((<any>window).mode.isMobile() || this.sectionProperties.docLayer._docType === 'spreadsheet') {
if (this.sectionProperties.commentList.length > 0)
this.orderCommentList();
return; // No adjustments for Calc, since only one comment can be shown at a time and that comment is shown at its belonging cell.
}
if (this.sectionProperties.commentList.length > 0) {
this.orderCommentList();
this.resetCommentsSize();
var isRTL = document.documentElement.dir === 'rtl';
var topRight: Array<number> = [this.myTopLeft[0], this.myTopLeft[1] + this.sectionProperties.marginY - this.documentTopLeft[1]];
var yOrigin = null;
var selectedIndex = null;
var x = isRTL ? 0 : topRight[0];
var availableSpace = this.calculateAvailableSpace();
if (availableSpace > this.sectionProperties.commentWidth) {
if (isRTL)
x = Math.round((this.containerObject.getDocumentAnchorSection().size[0] - app.file.size.pixels[0]) * 0.5) - this.containerObject.getDocumentAnchorSection().size[0];
else
x = topRight[0] - Math.round((this.containerObject.getDocumentAnchorSection().size[0] - app.file.size.pixels[0]) * 0.5);
} else if (isRTL)
x = -this.containerObject.getDocumentAnchorSection().size[0];
else
x -= this.sectionProperties.collapsedMarginToTheEdge;
if (this.sectionProperties.selectedComment) {
selectedIndex = this.getRootIndexOf(this.sectionProperties.selectedComment.sectionProperties.data.id);
this.sectionProperties.commentList[selectedIndex].sectionProperties.data.anchorPix = this.numberArrayToCorePixFromTwips(this.sectionProperties.commentList[selectedIndex].sectionProperties.data.anchorPos, 0, 2);
this.sectionProperties.commentList[selectedIndex].sectionProperties.data.anchorPix[1];
yOrigin = this.sectionProperties.commentList[selectedIndex].sectionProperties.data.anchorPix[1] - this.documentTopLeft[1];
var tempCrd: Array<number> = this.sectionProperties.commentList[selectedIndex].sectionProperties.data.anchorPix;
var resolved:string = this.sectionProperties.commentList[selectedIndex].sectionProperties.data.resolved;
if (!resolved || resolved === 'false' || this.sectionProperties.showResolved) {
var posX = isRTL ? (this.containerObject.getDocumentAnchorSection().size[0] + x + 15) : x;
this.showArrow([tempCrd[0], tempCrd[1]], [posX, tempCrd[1]]);
}
}
else {
this.hideArrow();
app.sectionContainer.requestReDraw();
}
var lastY = 0;
if (selectedIndex) {
this.loopUp(selectedIndex - 1, x, yOrigin);
lastY = this.loopDown(selectedIndex, x, yOrigin);
}
else {
lastY = this.loopDown(0, x, topRight[1]);
}
}
this.resizeComments();
lastY += this.containerObject.getDocumentTopLeft()[1];
if (lastY > app.file.size.pixels[1]) {
app.view.size.pixels[1] = lastY;
this.containerObject.requestReDraw();
}
else
app.view.size.pixels[1] = app.file.size.pixels[1];
}
private layout (zoom: any = null): void {
if (zoom)
this.doLayout();
else if (!this.sectionProperties.layoutTimer) {
this.sectionProperties.layoutTimer = setTimeout(function() {
delete this.sectionProperties.layoutTimer;
this.doLayout();
}.bind(this), 10 /* ms */);
} // else - avoid excessive re-layout
}
private update (): void {
if (this.sectionProperties.docLayer._docType === 'text')
this.updateThreadInfoIndicator();
this.layout();
}
private updateThreadInfoIndicator(): void {
for (var i = 0; i < this.sectionProperties.commentList.length; i++) {
var comment = this.sectionProperties.commentList[i];
var replyCount = 0;
var anyEdit = false;
if (comment && comment.isRootComment()) {
var lastIndex = this.getLastChildIndexOf(comment.sectionProperties.data.id);
var j = i;
while (this.sectionProperties.commentList[j] && j <= lastIndex) {
anyEdit = this.sectionProperties.commentList[j].isEdit() || anyEdit;
if (this.sectionProperties.commentList[j].sectionProperties.data.parent !== '0') {
if ((this.sectionProperties.commentList[j].sectionProperties.data.layoutStatus !== CommentLayoutStatus.DELETED ||
this.map['stateChangeHandler'].getItemValue('.uno:ShowTrackedChanges') === 'true') &&
this.sectionProperties.commentList[j].sectionProperties.data.resolved !== 'true') {
replyCount++;
}
}
j++;
}
}
if (anyEdit)
comment.updateThreadInfoIndicator('!');
else
comment.updateThreadInfoIndicator(replyCount);
}
}
// Returns the root comment index of given id
private getRootIndexOf (id: any): number {
var index = this.getIndexOf(id);
while (index >= 0) {
if (this.sectionProperties.commentList[index].sectionProperties.data.parent !== '0')
index--;
else
break;
}
return index;
}
// Returns the sub-root comment index of given id
private getSubRootIndexOf (id: any): number {
var index = this.getIndexOf(id);
if (index !== -1)
{
var comment = this.sectionProperties.commentList[index];
var parentId = comment.sectionProperties.data.parent;
while (index >= 0) {
if (this.sectionProperties.commentList[index].sectionProperties.data.id !== parentId && this.sectionProperties.commentList[index].sectionProperties.data.parent !== '0')
index--;
else
break;
}
}
return index;
}
public setViewResolved (state: any): void {
this.sectionProperties.showResolved = state;
for (var idx = 0; idx < this.sectionProperties.commentList.length;idx++) {
if (this.sectionProperties.commentList[idx].sectionProperties.data.resolved === 'true') {
if (state==false) {
if (this.sectionProperties.selectedComment == this.sectionProperties.commentList[idx]) {
this.unselect();
}
this.sectionProperties.commentList[idx].hide();
} else {
this.sectionProperties.commentList[idx].show();
}
}
this.sectionProperties.commentList[idx].update();
}
this.update();
}
private orderCommentList (): void {
this.sectionProperties.commentList.sort(function(a: any, b: any) {
return Math.abs(a.sectionProperties.data.anchorPos[1]) - Math.abs(b.sectionProperties.data.anchorPos[1]) ||
Math.abs(a.sectionProperties.data.anchorPos[0]) - Math.abs(b.sectionProperties.data.anchorPos[0]);
});
if (this.sectionProperties.docLayer._docType === 'text')
this.orderTextComments();
// idIndexMap is now invalid, update it.
this.updateIdIndexMap();
}
// reset theis size to default (100px text)
private resetCommentsSize (): void {
if (this.sectionProperties.docLayer._docType === 'text') {
const minMaxHeight = Number(getComputedStyle(document.documentElement).getPropertyValue('--annotation-min-size'));
for (var i = 0; i < this.sectionProperties.commentList.length;i++) {
if (this.sectionProperties.commentList[i].sectionProperties.contentNode.style.display !== 'none')
this.sectionProperties.commentList[i].sectionProperties.contentNode.setAttribute('style', 'max-height: '+minMaxHeight+'px');
}
if (this.sectionProperties.selectedComment) {
if (this.sectionProperties.selectedComment.sectionProperties.contentNode.style.display !== 'none') {
const maxMaxHeight = Number(getComputedStyle(document.documentElement).getPropertyValue('--annotation-max-size'));
this.sectionProperties.selectedComment.sectionProperties.contentNode.setAttribute('style', 'max-height: '+maxMaxHeight+'px');
}
}
}
}
// grow comments size if they have more text, and there is enought space between other comments
private resizeComments (): void {
// Change it true, if comments are allowed to grow up direction.
// Now it is disabled, because without constant indicator of the comments anchor, it can be confusing.
var growUp = false;
if (this.sectionProperties.docLayer._docType === 'text') {
const minMaxHeight = Number(getComputedStyle(document.documentElement).getPropertyValue('--annotation-min-size'));
const maxMaxHeight = Number(getComputedStyle(document.documentElement).getPropertyValue('--annotation-max-size'));
for (var i = 0; i < this.sectionProperties.commentList.length;i++) {
// Only if ContentNode is displayed.
if (this.sectionProperties.commentList[i].sectionProperties.contentNode.style.display !== 'none'
&& !this.sectionProperties.commentList[i].isEdit()) {
// act commentText height
var actHeight = this.sectionProperties.commentList[i].sectionProperties.contentText.getBoundingClientRect().height;
// if the comment is taller then minimal, we may want to make it taller
if (actHeight > minMaxHeight) {
// but we don't want to make it taller then the maximum
if (actHeight > maxMaxHeight) {
actHeight = maxMaxHeight;
}
// check if there is more space after this commit
var maxSize = maxMaxHeight;
if (i+1 < this.sectionProperties.commentList.length)
// max size of text should be the space between comments - size of non text parts
maxSize = this.sectionProperties.commentList[i+1].sectionProperties.container._leaflet_pos.y
- this.sectionProperties.commentList[i].sectionProperties.container._leaflet_pos.y
- this.sectionProperties.commentList[i].sectionProperties.author.getBoundingClientRect().height
- 3 * this.sectionProperties.marginY //top/bottom of comment window + space between comments
- 2; // not sure why
if (maxSize > maxMaxHeight) {
maxSize = maxMaxHeight;
} else if (growUp && actHeight > maxSize) {
// if more space needed as we have after the comment
// check it there is any space before the comment
var spaceBefore = this.sectionProperties.commentList[i].sectionProperties.container._leaflet_pos.y;
if (i > 0) {
spaceBefore -= this.sectionProperties.commentList[i-1].sectionProperties.container._leaflet_pos.y
+ this.sectionProperties.commentList[i-1].sectionProperties.container.getBoundingClientRect().height
+ this.sectionProperties.marginY;
} else {
spaceBefore += this.documentTopLeft[1];
}
// if there is more space
if (spaceBefore > 0) {
var moveUp = 0;
if (actHeight - maxSize < spaceBefore) {
// there is enought space, move up as much as we can;
moveUp = actHeight - maxSize;
} else {
// there is not enought space
moveUp = spaceBefore;
}
// move up
var posX = this.sectionProperties.commentList[i].sectionProperties.container._leaflet_pos.x;
var posY = this.sectionProperties.commentList[i].sectionProperties.container._leaflet_pos.y-moveUp;
(new L.PosAnimation()).run(this.sectionProperties.commentList[i].sectionProperties.container, {x: Math.round(posX), y: Math.round(posY)});
// increase comment height
maxSize += moveUp;
}
}
if (maxSize > minMaxHeight)
this.sectionProperties.commentList[i].sectionProperties.contentNode.setAttribute('style', 'max-height: '+Math.round(maxSize)+'px');
}
}
}
}
}
private updateIdIndexMap(): void {
this.idIndexMap.clear();
const commentList = this.sectionProperties.commentList;
for (var idx = 0; idx < commentList.length; idx++) {
const comment = commentList[idx];
console.assert(comment.sectionProperties && comment.sectionProperties.data, 'no sectionProperties.data!');
this.idIndexMap.set(comment.sectionProperties.data.id, idx);
}
}
private turnIntoAList (commentList: any): any[] {
var newArray;
if (!Array.isArray(commentList)) {
newArray = new Array(0);
for (var prop in commentList) {
if (Object.prototype.hasOwnProperty.call(commentList, prop)) {
newArray.push(commentList[prop]);
}
}
}
else {
newArray = commentList;
}
return newArray;
}
private addUpdateChildGroups() {
var parentCommentList: Array<any> = [];
for (var i = 0; i < this.sectionProperties.commentList.length; i++) {
var comment = this.sectionProperties.commentList[i];
comment.sectionProperties.children = [];
if (comment.sectionProperties.data.parent !== '0')
{
if (!parentCommentList.includes(comment.sectionProperties.data.parent))
parentCommentList.push(comment.sectionProperties.data.parent);
}
}
for (var i = 0; i < parentCommentList.length; i++) {
var parentComment;
for (var j = 0; j < this.sectionProperties.commentList.length; j++) {
if (this.sectionProperties.commentList[j].sectionProperties.data.id === parentCommentList[i]) {
parentComment = this.sectionProperties.commentList[j];
break;
}
}
if (parentComment) {
for (var j = 0; j < this.sectionProperties.commentList.length; j++) {
if (this.sectionProperties.commentList[j].sectionProperties.data.parent === parentCommentList[i])
parentComment.sectionProperties.children.push(this.sectionProperties.commentList[j]);
}
}
else
console.warn('Couldn\'t find parent comment.');
}
}
private addChildrenCommentsToList(comment: any, newOrder: Array<any>) {
comment.sectionProperties.children.forEach(function(element: any) {
newOrder.push(element);
if (element.sectionProperties.children.length > 0)
this.addChildrenCommentsToList(element, newOrder);
}.bind(this));
}
private orderTextComments() {
var newOrder = [];
for (var i = 0; i < this.sectionProperties.commentList.length; i++) {
var comment = this.sectionProperties.commentList[i];
if (comment.isRootComment()) {
newOrder.push(comment);
if (comment.sectionProperties.children.length > 0)
this.addChildrenCommentsToList(comment, newOrder);
}
}
this.sectionProperties.commentList = newOrder;
}
public importComments (commentList: any): void {
var comment;
this.clearList();
commentList = this.turnIntoAList(commentList);
if (commentList.length > 0) {
for (var i = 0; i < commentList.length; i++) {
comment = commentList[i];
this.adjustComment(comment);
if (comment.author in this.map._viewInfoByUserName) {
comment.avatar = this.map._viewInfoByUserName[comment.author].userextrainfo.avatar;
}
var commentSection = new cool.Comment(comment, {}, this);
if (!this.containerObject.addSection(commentSection))
continue;
this.sectionProperties.commentList.push(commentSection);
this.idIndexMap.set(commentSection.sectionProperties.data.id, i);
}
if (this.sectionProperties.docLayer._docType === 'text')
this.addUpdateChildGroups();
this.orderCommentList();
this.checkSize();
this.update();
}
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.hideAllComments(); // Apply drawing orders.
if (!(<any>window).mode.isMobile() && (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing'))
this.showHideComments();
}
// Accepts redlines/changes comments.
public importChanges (changesList: any): void {
var changeComment;
this.clearChanges();
changesList = this.turnIntoAList(changesList);
if (changesList.length > 0) {
for (var i = 0; i < changesList.length; i++) {
changeComment = changesList[i];
if (!this.adjustRedLine(changeComment))
// something wrong in this redline, skip this one
continue;
if (changeComment.author in this.map._viewInfoByUserName) {
changeComment.avatar = this.map._viewInfoByUserName[changeComment.author].userextrainfo.avatar;
}
var commentSection = new cool.Comment(changeComment, {}, this);
if (!this.containerObject.addSection(commentSection))
continue;
this.sectionProperties.commentList.push(commentSection);
}
this.orderCommentList();
this.checkSize();
this.update();
}
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.hideAllComments(); // Apply drawing orders.
}
// Remove redline comments.
private clearChanges(): void {
this.containerObject.pauseDrawing();
for (var i: number = this.sectionProperties.commentList.length -1; i > -1; i--) {
if (this.sectionProperties.commentList[i].sectionProperties.data.trackchange) {
this.containerObject.removeSection(this.sectionProperties.commentList[i].name);
this.sectionProperties.commentList.splice(i, 1);
}
}
this.updateIdIndexMap();
this.containerObject.resumeDrawing();
this.sectionProperties.selectedComment = null;
this.checkSize();
}
// Remove only text comments from the document (excluding change tracking comments)
private clearList (): void {
this.containerObject.pauseDrawing();
for (var i: number = this.sectionProperties.commentList.length -1; i > -1; i--) {
if (!this.sectionProperties.commentList[i].sectionProperties.data.trackchange) {
this.containerObject.removeSection(this.sectionProperties.commentList[i].name);
this.sectionProperties.commentList.splice(i, 1);
}
}
this.updateIdIndexMap();
this.containerObject.resumeDrawing();
this.sectionProperties.selectedComment = null;
this.checkSize();
}
public onCommentsDataUpdate(): void {
for (var i: number = this.sectionProperties.commentList.length -1; i > -1; i--) {
var comment = this.sectionProperties.commentList[i];
if (!comment.valid) {
comment.sectionProperties.commentListSection.removeItem(comment.sectionProperties.data.id);
}
comment.onCommentDataUpdate();
}
}
public rejectAllTrackedCommentChanges(): void {
for (var i = 0; i < this.sectionProperties.commentList.length; i++) {
var comment = this.sectionProperties.commentList[i];
if (comment.sectionProperties.data.layoutStatus === CommentLayoutStatus.DELETED) {
comment.sectionProperties.data.layoutStatus = CommentLayoutStatus.VISIBLE;
comment.sectionProperties.container.classList.remove('tracked-deleted-comment-show');
}
}
}
}
}
app.definitions.CommentSection = cool.CommentSection;