collabora-online/browser/src/canvas/sections/CommentSection.ts

1427 lines
57 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/.
*/
/* See CanvasSectionContainer.ts for explanations. */
declare var L: any;
declare var app: any;
declare var _: any;
declare var Autolinker: any;
declare var Hammer: any;
namespace cool {
/*
data.layoutStatus: Enumartion sent from the core side.
0: INVISIBLE, 1: VISIBLE, 2: INSERTED, 3: DELETED, 4: NONE, 5: HIDDEN
Ex: "DELETED" means that the comment is deleted while the "track changes" is on.
*/
export enum CommentLayoutStatus {
INVISIBLE,
VISIBLE,
INSERTED,
DELETED,
NONE,
HIDDEN
}
export class Comment extends CanvasSectionObject {
valid: boolean = true;
map: any;
pendingInit: boolean = true;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
constructor (data: any, options: any, commentListSectionPointer: cool.CommentSection) {
super({
name: L.CSections.Comment.name,
backgroundColor: '',
borderColor: null,
anchor: [],
position: [0, 0],
size: [],
expand: '',
processingOrder: L.CSections.Comment.processingOrder,
drawingOrder: L.CSections.Comment.drawingOrder,
zIndex: L.CSections.Comment.zIndex,
interactable: true,
sectionProperties: {},
});
this.myTopLeft = [0, 0];
this.documentObject = true;
this.map = L.Map.THIS;
if (!options)
options = {};
this.sectionProperties.commentListSection = commentListSectionPointer;
this.sectionProperties.docLayer = this.map._docLayer;
this.sectionProperties.selectedAreaPoint = null;
this.sectionProperties.cellCursorPoint = null;
this.sectionProperties.draggingStarted = false;
this.sectionProperties.dragStartPosition = null;
this.sectionProperties.minWidth = options.minWidth ? options.minWidth : 160;
this.sectionProperties.maxHeight = options.maxHeight ? options.maxHeight : 50;
this.sectionProperties.imgSize = options.imgSize ? options.imgSize : [32, 32];
this.sectionProperties.margin = options.margin ? options.margin : [40, 40];
this.sectionProperties.noMenu = options.noMenu ? options.noMenu : false;
if (data.parent === undefined)
data.parent = '0';
this.sectionProperties.data = data;
/*
possibleParentCommentId:
* User deletes a parent comment.
* User deletes also its child comment.
* User reverts the last change (deletion of child comment).
* A comment "Add" action is sent from the core side.
* The child comment has also its parent id.
* But there is no such parent at the moment.
* So we will remember its possible parent comment in case user also reverts the deletion of parent comment.
* In that case, parent comment will come with its old id.
* Child comment can now find its parent.
* We will check child comment to see if its parent has also been revived.
*/
this.sectionProperties.possibleParentCommentId = null;
this.sectionProperties.annotationMarker = null;
this.sectionProperties.wrapper = null;
this.sectionProperties.container = null;
this.sectionProperties.author = null;
this.sectionProperties.resolvedTextElement = null;
this.sectionProperties.authorAvatarImg = null;
this.sectionProperties.authorAvatartdImg = null;
this.sectionProperties.contentAuthor = null;
this.sectionProperties.contentDate = null;
this.sectionProperties.acceptButton = null;
this.sectionProperties.rejectButton = null;
this.sectionProperties.menu = null;
this.sectionProperties.captionNode = null;
this.sectionProperties.captionText = null;
this.sectionProperties.contentNode = null;
this.sectionProperties.nodeModify = null;
this.sectionProperties.nodeModifyText = null;
this.sectionProperties.contentText = null;
this.sectionProperties.nodeReply = null;
this.sectionProperties.nodeReplyText = null;
this.sectionProperties.contextMenu = false;
this.sectionProperties.highlightedTextColor = '#777777'; // Writer.
this.sectionProperties.usedTextColor = this.sectionProperties.data.color; // Writer.
this.sectionProperties.showSelectedCoordinate = true; // Writer.
if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') {
this.sectionProperties.parthash = this.sectionProperties.data.parthash;
this.sectionProperties.partIndex = this.sectionProperties.docLayer._partHashes.indexOf(String(this.sectionProperties.parthash));
}
this.sectionProperties.isHighlighted = false;
this.name = data.id === 'new' ? 'new comment': 'comment ' + data.id;
this.sectionProperties.isRemoved = false;
this.sectionProperties.children = []; // This is used for Writer comments. There is parent / child relationship between comments in Writer files.
this.convertRectanglesToCoreCoordinates(); // Convert rectangle coordiantes into core pixels on initialization.
}
// Comments import can be costly if the document has a lot of them. If they are all imported/initialized
// when online gets comments message from core, the initial doc render is delayed. To avoid that we do
// lazy import of each comment when it needs to be shown (based on its coordinates).
private doPendingInitializationInView (force: boolean = false): void {
if (!this.pendingInit)
return;
if (!force && !this.convertRectanglesToViewCoordinates())
return;
var button = L.DomUtil.create('div', 'annotation-btns-container', this.sectionProperties.nodeModify);
L.DomEvent.on(this.sectionProperties.nodeModifyText, 'blur', this.onLostFocus, this);
L.DomEvent.on(this.sectionProperties.nodeReplyText, 'blur', this.onLostFocusReply, this);
L.DomEvent.on(this.sectionProperties.nodeModifyText, 'input', this.textAreaInput, this);
L.DomEvent.on(this.sectionProperties.nodeReplyText, 'input', this.textAreaInput, this);
this.createButton(button, 'annotation-cancel-' + this.sectionProperties.data.id, 'annotation-button button-secondary', _('Cancel'), this.handleCancelCommentButton);
this.createButton(button, 'annotation-save-' + this.sectionProperties.data.id, 'annotation-button button-primary',_('Save'), this.handleSaveCommentButton);
button = L.DomUtil.create('div', '', this.sectionProperties.nodeReply);
this.createButton(button, 'annotation-cancel-reply-' + this.sectionProperties.data.id, 'annotation-button button-secondary', _('Cancel'), this.handleCancelCommentButton);
this.createButton(button, 'annotation-reply-' + this.sectionProperties.data.id, 'annotation-button button-primary', _('Reply'), this.handleReplyCommentButton);
L.DomEvent.disableScrollPropagation(this.sectionProperties.container);
// Since this is a late called function, if the width is enough, we shouldn't collapse the comments.
if (this.sectionProperties.docLayer._docType !== 'text' || this.sectionProperties.commentListSection.isCollapsed === true)
this.sectionProperties.container.style.visibility = 'hidden';
this.sectionProperties.nodeModify.style.display = 'none';
this.sectionProperties.nodeReply.style.display = 'none';
var events = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'keydown', 'keypress', 'keyup', 'touchstart', 'touchmove', 'touchend'];
L.DomEvent.on(this.sectionProperties.container, 'click', this.onMouseClick, this);
L.DomEvent.on(this.sectionProperties.container, 'keydown', this.onEscKey, this);
L.DomEvent.on(this.sectionProperties.container, 'wheel', app.sectionContainer.onMouseWheel, app.sectionContainer);
L.DomEvent.on(this.sectionProperties.contentNode, 'wheel', this.onMouseWheel, this);
for (var it = 0; it < events.length; it++) {
L.DomEvent.on(this.sectionProperties.container, events[it], L.DomEvent.stopPropagation, this);
}
L.DomEvent.on(this.sectionProperties.container, 'touchstart',
function (e: TouchEvent) {
if (e && e.touches.length > 1) {
L.DomEvent.preventDefault(e);
}
},
this);
this.update();
this.pendingInit = false;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public onMouseWheel (e: any) : void {
if (e.currentTarget.clientHeight === e.currentTarget.scrollHeight)
return;
var _scrollTop = e.currentTarget.scrollTop;
if (e.deltaY < 0 && _scrollTop > 0)
e.stopPropagation();
else if (e.deltaY > 0 && _scrollTop + $(e.currentTarget).height() < e.target.scrollHeight)
e.stopPropagation();
}
public onInitialize (): void {
this.createContainerAndWrapper();
this.createAuthorTable();
if (this.sectionProperties.data.trackchange && !this.map.isReadOnlyMode()) {
this.createTrackChangeButtons();
}
if (this.sectionProperties.noMenu !== true && app.isCommentEditingAllowed()) {
this.createMenu();
}
if (this.sectionProperties.data.trackchange) {
this.sectionProperties.captionNode = L.DomUtil.create('div', 'cool-annotation-caption', this.sectionProperties.wrapper);
this.sectionProperties.captionText = L.DomUtil.create('div', '', this.sectionProperties.captionNode);
}
this.sectionProperties.contentNode = L.DomUtil.create('div', 'cool-annotation-content cool-dont-break', this.sectionProperties.wrapper);
this.sectionProperties.contentNode.id = 'annotation-content-area-' + this.sectionProperties.data.id;
this.sectionProperties.nodeModify = L.DomUtil.create('div', 'cool-annotation-edit' + ' modify-annotation', this.sectionProperties.wrapper);
this.sectionProperties.nodeModifyText = L.DomUtil.create('textarea', 'cool-annotation-textarea', this.sectionProperties.nodeModify);
this.sectionProperties.nodeModifyText.id = 'annotation-modify-textarea-' + this.sectionProperties.data.id;
this.sectionProperties.contentText = L.DomUtil.create('div', '', this.sectionProperties.contentNode);
this.sectionProperties.nodeReply = L.DomUtil.create('div', 'cool-annotation-edit' + ' reply-annotation', this.sectionProperties.wrapper);
this.sectionProperties.nodeReplyText = L.DomUtil.create('textarea', 'cool-annotation-textarea', this.sectionProperties.nodeReply);
this.sectionProperties.nodeReplyText.id = 'annotation-reply-textarea-' + this.sectionProperties.data.id;
this.sectionProperties.container.style.visibility = 'hidden';
this.doPendingInitializationInView();
}
private createContainerAndWrapper (): void {
var isRTL = document.documentElement.dir === 'rtl';
this.sectionProperties.container = L.DomUtil.create('div', 'cool-annotation' + (isRTL ? ' rtl' : ''));
this.sectionProperties.container.id = 'comment-container-' + this.sectionProperties.data.id;
this.sectionProperties.container.addEventListener('focusin', this.onContainerGotFocus.bind(this));
this.sectionProperties.container.addEventListener('focusout', this.onContainerLostFocus.bind(this));
var mobileClass = (<any>window).mode.isMobile() ? ' wizard-comment-box': '';
if (this.sectionProperties.data.trackchange) {
this.sectionProperties.wrapper = L.DomUtil.create('div', 'cool-annotation-redline-content-wrapper' + mobileClass, this.sectionProperties.container);
} else {
this.sectionProperties.wrapper = L.DomUtil.create('div', 'cool-annotation-content-wrapper' + mobileClass, this.sectionProperties.container);
}
if (!(<any>window).mode.isMobile())
document.getElementById('document-container').appendChild(this.sectionProperties.container);
// We make comment directly visible when its transitioned to its determined position
if (cool.CommentSection.autoSavedComment)
this.sectionProperties.container.style.visibility = 'hidden';
}
private onContainerGotFocus() {
app.view.commentHasFocus = true;
}
private onContainerLostFocus() {
app.view.commentHasFocus = false;
}
private createAuthorTable (): void {
this.sectionProperties.author = L.DomUtil.create('table', 'cool-annotation-table', this.sectionProperties.wrapper);
var tbody = L.DomUtil.create('tbody', '', this.sectionProperties.author);
var rowResolved = L.DomUtil.create('tr', '', tbody);
var tdResolved = L.DomUtil.create('td', 'cool-annotation-resolved', rowResolved);
var pResolved = L.DomUtil.create('div', 'cool-annotation-content-resolved', tdResolved);
this.sectionProperties.resolvedTextElement = pResolved;
this.updateResolvedField(this.sectionProperties.data.resolved);
var tr = L.DomUtil.create('tr', '', tbody);
this.sectionProperties.authorRow = tr;
tr.id = 'author table row ' + this.sectionProperties.data.id;
var tdImg = L.DomUtil.create('td', 'cool-annotation-img', tr);
var tdAuthor = L.DomUtil.create('td', 'cool-annotation-author', tr);
var imgAuthor = L.DomUtil.create('img', 'avatar-img', tdImg);
var viewId = this.map.getViewId(this.sectionProperties.data.author);
L.LOUtil.setUserImage(imgAuthor, this.map, viewId);
imgAuthor.setAttribute('width', this.sectionProperties.imgSize[0]);
imgAuthor.setAttribute('height', this.sectionProperties.imgSize[1]);
if (this.sectionProperties.docLayer._docType !== 'spreadsheet') {
this.sectionProperties.collapsedInfoNode = L.DomUtil.create('div', 'cool-annotation-info-collapsed', tdImg);
this.sectionProperties.collapsedInfoNode.style.display = 'none';
}
this.sectionProperties.authorAvatarImg = imgAuthor;
this.sectionProperties.authorAvatartdImg = tdImg;
this.sectionProperties.contentAuthor = L.DomUtil.create('div', 'cool-annotation-content-author', tdAuthor);
this.sectionProperties.contentDate = L.DomUtil.create('div', 'cool-annotation-date', tdAuthor);
this.sectionProperties.autoSave = L.DomUtil.create('div', 'cool-annotation-autosavelabel', tdAuthor);
}
private createMenu (): void {
var tdMenu = L.DomUtil.create('td', 'cool-annotation-menubar', this.sectionProperties.authorRow);
this.sectionProperties.menu = L.DomUtil.create('div', this.sectionProperties.data.trackchange ? 'cool-annotation-menu-redline' : 'cool-annotation-menu', tdMenu);
this.sectionProperties.menu.id = 'comment-annotation-menu-' + this.sectionProperties.data.id;
this.sectionProperties.menu.tabIndex = 0;
this.sectionProperties.menu.onclick = this.menuOnMouseClick.bind(this);
this.sectionProperties.menu.onkeypress = this.menuOnKeyPress.bind(this);
var divMenuTooltipText = _('Open menu');
this.sectionProperties.menu.dataset.title = divMenuTooltipText;
this.sectionProperties.menu.setAttribute('aria-label', divMenuTooltipText);
this.sectionProperties.menu.annotation = this;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public setData (data: any): void {
this.sectionProperties.data = data;
}
private createTrackChangeButtons (): void {
var tdAccept = L.DomUtil.create('td', 'cool-annotation-menubar', this.sectionProperties.authorRow);
var acceptButton = this.sectionProperties.acceptButton = L.DomUtil.create('button', 'cool-redline-accept-button', tdAccept);
var tdReject = L.DomUtil.create('td', 'cool-annotation-menubar', this.sectionProperties.authorRow);
var rejectButton = this.sectionProperties.rejectButton = L.DomUtil.create('button', 'cool-redline-reject-button', tdReject);
acceptButton.dataset.title = _('Accept change');
acceptButton.setAttribute('aria-label', _('Accept change'));
L.DomEvent.on(acceptButton, 'click', function() {
this.map.fire('RedlineAccept', {id: this.sectionProperties.data.id});
}, this);
rejectButton.dataset.title = _('Reject change');
rejectButton.setAttribute('aria-label', _('Reject change'));
L.DomEvent.on(rejectButton, 'click', function() {
this.map.fire('RedlineReject', {id: this.sectionProperties.data.id});
}, this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private createButton (container: any, id: any, cssClass: string, value: any, handler: any): void {
var button = L.DomUtil.create('input', cssClass, container);
button.id = id;
button.type = 'button';
button.value = value;
L.DomEvent.on(button, 'mousedown', L.DomEvent.preventDefault);
L.DomEvent.on(button, 'click', handler, this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public parentOf (comment: any): boolean {
return this.sectionProperties.data.id === comment.sectionProperties.data.parent;
}
public updateResolvedField (state: string): void {
this.sectionProperties.resolvedTextElement.innerText = state === 'true' ? _('Resolved') : '';
}
private textAreaInput (): void {
this.sectionProperties.autoSave.innerText = '';
}
private updateContent (): void {
this.sectionProperties.contentText.innerText = this.sectionProperties.data.text ? this.sectionProperties.data.text: '';
// Get the escaped HTML out and find for possible, useful links
var linkedText = Autolinker.link(this.sectionProperties.contentText.outerHTML);
// Set the property of text field directly. This is insecure otherwise because it doesn't escape the input
// But we have already escaped the input before and only thing we are adding on top of that is Autolinker
// generated text.
this.sectionProperties.contentText.innerHTML = linkedText;
// Original unlinked text
this.sectionProperties.contentText.origText = this.sectionProperties.data.text ? this.sectionProperties.data.text: '';
this.sectionProperties.nodeModifyText.textContent = this.sectionProperties.data.text ? this.sectionProperties.data.text: '';
this.sectionProperties.nodeModifyText.value = this.sectionProperties.data.text ? this.sectionProperties.data.text: '';
this.sectionProperties.contentAuthor.innerText = this.sectionProperties.data.author;
this.updateResolvedField(this.sectionProperties.data.resolved);
if (this.sectionProperties.data.avatar) {
this.sectionProperties.authorAvatarImg.setAttribute('src', this.sectionProperties.data.avatar);
}
else {
$(this.sectionProperties.authorAvatarImg).css('padding-top', '4px');
}
var user = this.map.getViewId(this.sectionProperties.data.author);
if (user >= 0) {
var color = L.LOUtil.rgbToHex(this.map.getViewColor(user));
this.sectionProperties.authorAvatartdImg.style.borderColor = color;
}
var d = new Date(this.sectionProperties.data.dateTime.replace(/,.*/, 'Z'));
var dateOptions: any = { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' };
this.sectionProperties.contentDate.innerText = isNaN(d.getTime()) ? this.sectionProperties.data.dateTime: d.toLocaleDateString((<any>String).locale, dateOptions);
if (this.sectionProperties.data.trackchange) {
this.sectionProperties.captionText.innerText = this.sectionProperties.data.description;
}
}
private updateLayout (): void {
var style = this.sectionProperties.wrapper.style;
style.width = '';
style.whiteSpace = 'nowrap';
style.whiteSpace = '';
}
private setPositionAndSize (): void {
var rectangles = this.sectionProperties.data.rectanglesOriginal;
if (rectangles && this.sectionProperties.docLayer._docType === 'text') {
var xMin: number = Infinity, yMin: number = Infinity, xMax: number = 0, yMax: number = 0;
for (var i = 0; i < rectangles.length; i++) {
if (rectangles[i][0] < xMin)
xMin = rectangles[i][0];
if (rectangles[i][1] < yMin)
yMin = rectangles[i][1];
if (rectangles[i][0] + rectangles[i][2] > xMax)
xMax = rectangles[i][0] + rectangles[i][2];
if (rectangles[i][1] + rectangles[i][3] > yMax)
yMax = rectangles[i][1] + rectangles[i][3];
}
// Rectangles are in twips. Convert them to core pixels.
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
xMin = Math.round(xMin * ratio);
yMin = Math.round(yMin * ratio);
xMax = Math.round(xMax * ratio);
yMax = Math.round(yMax * ratio);
this.setPosition(xMin, yMin); // This function is added by section container.
this.size = [xMax - xMin, yMax - yMin];
if (this.size[0] < 5)
this.size[0] = 5;
}
else if (this.sectionProperties.data.cellRange && this.sectionProperties.docLayer._docType === 'spreadsheet') {
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
this.size = this.calcCellSize();
var cellPos = this.map._docLayer._cellRangeToTwipRect(this.sectionProperties.data.cellRange).toRectangle();
let startX = cellPos[0];
if (this.isCalcRTL()) { // Mirroring is done in setPosition
const sizeX = cellPos[2];
startX += sizeX; // but adjust for width of the cell.
}
this.setShowSection(true);
var position: Array<number> = [Math.round(cellPos[0] * ratio), Math.round(cellPos[1] * ratio)];
var splitPosCore = {x: 0, y: 0};
if (this.map._docLayer.getSplitPanesContext())
splitPosCore = this.map._docLayer.getSplitPanesContext().getSplitPos();
splitPosCore.x *= app.dpiScale;
splitPosCore.y *= app.dpiScale;
if (position[0] < splitPosCore.x)
position[0] += this.documentTopLeft[0];
else if (position[0] - this.documentTopLeft[0] < splitPosCore.x)
this.setShowSection(false);
if (position[1] < splitPosCore.y)
position[1] += this.documentTopLeft[1];
else if (position[1] - this.documentTopLeft[1] < splitPosCore.y)
this.setShowSection(false);
this.setPosition(position[0], position[1]);
}
else if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') {
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
this.size = [Math.round(this.sectionProperties.imgSize[0] * app.dpiScale), Math.round(this.sectionProperties.imgSize[1] * app.dpiScale)];
this.setPosition(Math.round(this.sectionProperties.data.rectangle[0] * ratio), Math.round(this.sectionProperties.data.rectangle[1] * ratio));
}
}
public removeHighlight (): void {
if (this.sectionProperties.docLayer._docType === 'text') {
this.sectionProperties.usedTextColor = this.sectionProperties.data.color;
this.sectionProperties.isHighlighted = false;
}
else if (this.sectionProperties.docLayer._docType === 'spreadsheet') {
this.backgroundColor = null;
this.backgroundOpacity = 1;
}
}
public highlight (): void {
if (this.sectionProperties.docLayer._docType === 'text') {
this.sectionProperties.usedTextColor = this.sectionProperties.highlightedTextColor;
var x: number = Math.round(this.position[0] / app.dpiScale);
var y: number = Math.round(this.position[1] / app.dpiScale);
(this.containerObject.getSectionWithName(L.CSections.Scroll.name) as cool.ScrollSection).onScrollTo({x: x, y: y});
}
else if (this.sectionProperties.docLayer._docType === 'spreadsheet') {
this.backgroundColor = '#777777'; //background: rgba(119, 119, 119, 0.25);
this.backgroundOpacity = 0.25;
var x: number = Math.round(this.position[0] / app.dpiScale);
var y: number = Math.round(this.position[1] / app.dpiScale);
(this.containerObject.getSectionWithName(L.CSections.Scroll.name) as cool.ScrollSection).onScrollTo({x: x, y: y});
}
else if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') {
var x: number = Math.round(this.position[0] / app.dpiScale);
var y: number = Math.round(this.position[1] / app.dpiScale);
(this.containerObject.getSectionWithName(L.CSections.Scroll.name) as cool.ScrollSection).onScrollTo({x: x, y: y});
}
this.containerObject.requestReDraw();
this.sectionProperties.isHighlighted = true;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private static doesRectIntersectView(pos: number[], size: number[], viewContext: any): boolean {
var paneBoundsList = <any[]>viewContext.paneBoundsList;
var endPos = [pos[0] + size[0], pos[1] + size[1]];
for (var i = 0; i < paneBoundsList.length; ++i) {
var paneBounds = paneBoundsList[i];
var rectInvisible = (endPos[0] < paneBounds.min.x || endPos[1] < paneBounds.min.y ||
pos[0] > paneBounds.max.x || pos[1] > paneBounds.max.y);
if (!rectInvisible)
return true;
}
return false;
}
/*
This function doesn't take topleft positions of sections into account.
This just returns bare pixel coordinates of the rectangles.
*/
private convertRectanglesToCoreCoordinates() {
var pixelBasedOrgRectangles = new Array<Array<number>>();
var originals = this.sectionProperties.data.rectanglesOriginal;
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
var pos: number[], size: number[];
if (originals) {
for (var i = 0; i < originals.length; i++) {
pos = [
Math.round(originals[i][0] * ratio),
Math.round(originals[i][1] * ratio)
];
size = [
Math.round(originals[i][2] * ratio),
Math.round(originals[i][3] * ratio)
];
pixelBasedOrgRectangles.push([pos[0], pos[1], size[0], size[1]]);
}
this.sectionProperties.pixelBasedOrgRectangles = pixelBasedOrgRectangles;
}
}
// This is for svg elements that will be bound to document-container.
// This also returns whether any rectangle has an intersection with the visible area/panes.
// This function calculates the core pixel coordinates then converts them into view coordinates.
private convertRectanglesToViewCoordinates () : boolean {
var rectangles = this.sectionProperties.data.rectangles;
var originals = this.sectionProperties.data.rectanglesOriginal;
var viewContext = this.map.getTileSectionMgr()._paintContext();
var intersectsVisibleArea = false;
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
var pos: number[], size: number[];
if (rectangles) {
var documentAnchorSection = this.containerObject.getDocumentAnchorSection();
var diff = [documentAnchorSection.myTopLeft[0] - this.documentTopLeft[0], documentAnchorSection.myTopLeft[1] - this.documentTopLeft[1]];
for (var i = 0; i < rectangles.length; i++) {
pos = [
Math.round(originals[i][0] * ratio),
Math.round(originals[i][1] * ratio)
];
size = [
Math.round(originals[i][2] * ratio),
Math.round(originals[i][3] * ratio)
];
if (!intersectsVisibleArea && Comment.doesRectIntersectView(pos, size, viewContext))
intersectsVisibleArea = true;
rectangles[i][0] = pos[0] + diff[0];
rectangles[i][1] = pos[1] + diff[1];
rectangles[i][2] = size[0];
rectangles[i][3] = size[1];
}
} else if (this.sectionProperties.data.trackchange && this.sectionProperties.data.anchorPos) {
// For redline comments there are no 'rectangles' or 'rectangleOriginal' properties in sectionProperties.data
// So use the comment rectangle stored in anchorPos (in display? twips).
pos = this.getPosition();
size = this.getSize();
intersectsVisibleArea = Comment.doesRectIntersectView(pos, size, viewContext);
}
return intersectsVisibleArea;
}
public getPosition (): number[] {
// For redline comments there are no 'rectangles' or 'rectangleOriginal' properties in sectionProperties.data
// So use the comment rectangle stored in anchorPos (in display? twips).
if (this.sectionProperties.data.trackchange && this.sectionProperties.data.anchorPos) {
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
var anchorPos = this.sectionProperties.data.anchorPos;
return [
Math.round(anchorPos[0] * ratio),
Math.round(anchorPos[1] * ratio)
];
} else {
return this.position;
}
}
public getSize(): number[] {
// For redline comments there are no 'rectangles' or 'rectangleOriginal' properties in sectionProperties.data
// So use the comment rectangle stored in anchorPos (in display? twips).
if (this.sectionProperties.data.trackchange && this.sectionProperties.data.anchorPos) {
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
var anchorPos = this.sectionProperties.data.anchorPos;
return [
Math.round(anchorPos[2] * ratio),
Math.round(anchorPos[3] * ratio)
];
} else {
return this.size;
}
}
private updatePosition (): void {
this.convertRectanglesToViewCoordinates();
this.convertRectanglesToCoreCoordinates();
this.setPositionAndSize();
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.positionCalcComment();
}
private updateAnnotationMarker (): void {
// Make sure to place the markers only for presentations and draw documents
if (this.sectionProperties.docLayer._docType !== 'presentation' && this.sectionProperties.docLayer._docType !== 'drawing')
return;
if (this.sectionProperties.data == null)
return;
if (this.sectionProperties.annotationMarker === null) {
this.sectionProperties.annotationMarker = L.marker(new L.LatLng(0, 0), {
icon: L.divIcon({
className: 'annotation-marker',
iconSize: null
}),
draggable: true
});
if (this.sectionProperties.docLayer._partHashes[this.sectionProperties.docLayer._selectedPart] === this.sectionProperties.data.parthash || app.file.fileBasedView)
this.map.addLayer(this.sectionProperties.annotationMarker);
}
if (this.sectionProperties.data.rectangle != null) {
this.sectionProperties.annotationMarker.setLatLng(this.sectionProperties.docLayer._twipsToLatLng(new L.Point(this.sectionProperties.data.rectangle[0], this.sectionProperties.data.rectangle[1])));
this.sectionProperties.annotationMarker.on('dragstart drag dragend', this.onMarkerDrag, this);
//this.sectionProperties.annotationMarker.on('click', this.onMarkerClick, this);
}
}
public isContainerVisible (): boolean {
return this.sectionProperties.container.style &&
this.sectionProperties.container.style.display !== 'none' &&
(
this.sectionProperties.container.style.visibility === 'visible' ||
this.sectionProperties.container.style.visibility === ''
);
}
public update (): void {
this.updateContent();
this.updateLayout();
this.updatePosition();
this.updateAnnotationMarker();
}
private showMarker (): void {
if (this.sectionProperties.annotationMarker != null) {
this.map.addLayer(this.sectionProperties.annotationMarker);
}
}
private hideMarker (): void {
if (this.sectionProperties.annotationMarker != null) {
this.map.removeLayer(this.sectionProperties.annotationMarker);
}
}
private showWriter() {
if (!this.isCollapsed || this.isSelected()) {
this.sectionProperties.container.style.visibility = '';
this.sectionProperties.container.style.display = '';
}
if (this.sectionProperties.data.resolved === 'false' || this.sectionProperties.commentListSection.sectionProperties.showResolved)
L.DomUtil.addClass(this.sectionProperties.container, 'cool-annotation-collapsed-show');
this.sectionProperties.contentNode.style.display = '';
this.sectionProperties.nodeModify.style.display = 'none';
this.sectionProperties.nodeReply.style.display = 'none';
this.sectionProperties.collapsedInfoNode.style.visibility = '';
this.sectionProperties.showSelectedCoordinate = true;
}
private showCalc() {
this.sectionProperties.container.style.display = '';
this.sectionProperties.contentNode.style.display = '';
this.sectionProperties.nodeModify.style.display = 'none';
this.sectionProperties.nodeReply.style.display = 'none';
this.positionCalcComment();
if (!(<any>window).mode.isMobile()) {
this.sectionProperties.commentListSection.select(this);
}
this.sectionProperties.container.style.visibility = '';
}
private getCommentWidth() {
// note: getComputedStyle can be an exceptional bottle-neck with many comments
return parseFloat(getComputedStyle(this.sectionProperties.container).width) * app.dpiScale;
}
public positionCalcComment(): void {
if (!(<any>window).mode.isMobile()) {
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
var cellPos = this.map._docLayer._cellRangeToTwipRect(this.sectionProperties.data.cellRange).toRectangle();
var originalSize = [Math.round((cellPos[2]) * ratio), Math.round((cellPos[3]) * ratio)];
const startX = this.isCalcRTL() ? this.myTopLeft[0] - this.getCommentWidth() : this.myTopLeft[0] + originalSize[0] - 3;
var pos: Array<number> = [Math.round(startX / app.dpiScale), Math.round(this.myTopLeft[1] / app.dpiScale)];
this.sectionProperties.container.style.transform = 'translate3d(' + pos[0] + 'px, ' + pos[1] + 'px, 0px)';
}
}
private showImpressDraw() {
if (this.isInsideActivePart()) {
this.sectionProperties.container.style.display = '';
this.sectionProperties.nodeModify.style.display = 'none';
this.sectionProperties.nodeReply.style.display = 'none';
this.sectionProperties.contentNode.style.display = '';
if (this.isSelected() || !this.isCollapsed) {
this.sectionProperties.container.style.visibility = '';
}
else {
this.sectionProperties.container.style.visibility = 'hidden';
}
L.DomUtil.addClass(this.sectionProperties.container, 'cool-annotation-collapsed-show');
}
}
public setLayoutClass(): void {
this.sectionProperties.container.classList.remove('tracked-deleted-comment-show');
this.sectionProperties.container.classList.remove('tracked-deleted-comment-hide');
var showTrackedChanges: boolean = this.map['stateChangeHandler'].getItemValue('.uno:ShowTrackedChanges') === 'true';
var layoutClass: string = showTrackedChanges ? 'tracked-deleted-comment-show' : 'tracked-deleted-comment-hide';
if (this.sectionProperties.data.layoutStatus === CommentLayoutStatus.DELETED) {
this.sectionProperties.container.classList.add(layoutClass);
}
}
public show(): void {
this.doPendingInitializationInView(true /* force */);
this.showMarker();
// On mobile, container shouldn't be 'document-container', but it is 'document-container' on initialization. So we hide the comment until comment wizard is opened.
if ((<any>window).mode.isMobile() && this.sectionProperties.container.parentElement === document.getElementById('document-container'))
this.sectionProperties.container.style.visibility = 'hidden';
if (cool.CommentSection.commentWasAutoAdded)
return;
if (this.sectionProperties.docLayer._docType === 'text')
this.showWriter();
else if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing')
this.showImpressDraw();
else if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.showCalc();
this.setLayoutClass();
}
private hideWriter() {
this.sectionProperties.container.style.visibility = 'hidden';
this.sectionProperties.nodeModify.style.display = 'none';
this.sectionProperties.nodeReply.style.display = 'none';
this.sectionProperties.showSelectedCoordinate = false;
L.DomUtil.removeClass(this.sectionProperties.container, 'cool-annotation-collapsed-show');
}
private hideCalc() {
this.sectionProperties.container.style.visibility = 'hidden';
this.sectionProperties.nodeModify.style.display = 'none';
this.sectionProperties.nodeReply.style.display = 'none';
if (this.sectionProperties.commentListSection.sectionProperties.selectedComment === this)
this.sectionProperties.commentListSection.sectionProperties.selectedComment = null;
}
private hideImpressDraw() {
if (!this.isInsideActivePart()) {
this.sectionProperties.container.style.display = 'none';
this.hideMarker();
}
else {
this.sectionProperties.container.style.display = '';
if (this.isCollapsed)
this.sectionProperties.container.style.visibility = 'hidden';
this.sectionProperties.nodeModify.style.display = 'none';
this.sectionProperties.nodeReply.style.display = 'none';
}
L.DomUtil.removeClass(this.sectionProperties.container, 'cool-annotation-collapsed-show');
}
// check if this is "our" autosaved comment
// core is not aware it's autosaved one so use this simplified detection based on content
public isAutoSaved (): boolean {
var autoSavedComment = cool.CommentSection.autoSavedComment;
if (!autoSavedComment)
return false;
var authorMatch = autoSavedComment.sectionProperties.data.author === this.sectionProperties.data.author;
return authorMatch;
}
public hide (): void {
if (this.isEdit()) {
return;
}
if (this.sectionProperties.data.id === 'new') {
this.sectionProperties.commentListSection.removeItem(this.sectionProperties.data.id);
return;
}
if (this.sectionProperties.docLayer._docType === 'text')
this.hideWriter();
else if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.hideCalc();
else if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing')
this.hideImpressDraw();
}
private isInsideActivePart() {
// Impress and Draw only.
return this.sectionProperties.partIndex === this.sectionProperties.docLayer._selectedPart;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private menuOnMouseClick (e: any): void {
$(this.sectionProperties.menu).contextMenu();
L.DomEvent.stopPropagation(e);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private menuOnKeyPress (e: any): void {
if (e.code === 'Space' || e.code === 'Enter')
$(this.sectionProperties.menu).contextMenu();
L.DomEvent.stopPropagation(e);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private onMouseClick (e: any): void {
if (((<any>window).mode.isMobile() || (<any>window).mode.isTablet())
&& this.map.getDocType() == 'spreadsheet'
&& !this.map.uiManager.mobileWizard.isOpen()) {
this.hide();
}
L.DomEvent.stopPropagation(e);
this.sectionProperties.commentListSection.click(this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private onEscKey (e: any): void {
if ((<any>window).mode.isDesktop()) {
if (e.keyCode === 27) {
this.onCancelClick(e);
} else if (e.keyCode === 33 /*PageUp*/ || e.keyCode === 34 /*PageDown*/) {
// work around for a chrome issue https://issues.chromium.org/issues/41417806
L.DomEvent.preventDefault(e);
var pos = e.keyCode === 33 ? 0 : e.target.textLength;
var currentPos = e.target.selectionStart;
if (e.shiftKey) {
var [start, end] = currentPos <= pos ? [currentPos, pos] : [pos, currentPos];
e.target.setSelectionRange(start, end, currentPos > pos ? 'backward' : 'forward');
} else {
e.target.setSelectionRange(pos, pos);
}
}
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public handleReplyCommentButton (e: any): void {
cool.CommentSection.autoSavedComment = null;
cool.CommentSection.commentWasAutoAdded = false;
this.textAreaInput();
this.onReplyClick(e);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public onReplyClick (e: any): void {
L.DomEvent.stopPropagation(e);
if ((<any>window).mode.isMobile()) {
this.sectionProperties.data.reply = this.sectionProperties.data.text;
this.sectionProperties.commentListSection.saveReply(this);
} else {
this.sectionProperties.data.reply = this.sectionProperties.nodeReplyText.value;
// Assigning an empty string to .innerHTML property in some browsers will convert it to 'null'
// While in browsers like Chrome and Firefox, a null value is automatically converted to ''
// Better to assign '' here instead of null to keep the behavior same for all
this.sectionProperties.nodeReplyText.value = '';
this.show();
this.sectionProperties.commentListSection.saveReply(this);
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public handleCancelCommentButton (e: any): void {
if (cool.CommentSection.commentWasAutoAdded) {
app.sectionContainer.getSectionWithName(L.CSections.CommentList.name).remove(this.sectionProperties.data.id);
}
if (cool.CommentSection.autoSavedComment) {
this.sectionProperties.contentText.origText = this.sectionProperties.contentText.unedited;
this.sectionProperties.contentText.unedited = null;
}
// These lines are repeated in onCancelClick,
// it makes things simple by not adding so many condition for different apps and different situation
// It is mandatory to change these values before handleSaveCommentButton is called
// calling handleSaveCommentButton in onCancelClick causes problem because that is also called from many other events/function (i.e: onPartChange)
this.sectionProperties.nodeModifyText.value = this.sectionProperties.contentText.origText;
this.sectionProperties.nodeReplyText.value = '';
if (cool.CommentSection.autoSavedComment)
this.handleSaveCommentButton(e);
this.onCancelClick(e);
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
this.hideCalc();
cool.CommentSection.commentWasAutoAdded = false;
cool.CommentSection.autoSavedComment = null;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public onCancelClick (e: any): void {
if (e)
L.DomEvent.stopPropagation(e);
this.sectionProperties.nodeModifyText.value = this.sectionProperties.contentText.origText;
this.sectionProperties.nodeReplyText.value = '';
if (this.sectionProperties.docLayer._docType !== 'spreadsheet')
this.show();
this.sectionProperties.commentListSection.cancel(this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public handleSaveCommentButton (e: any): void {
cool.CommentSection.autoSavedComment = null;
cool.CommentSection.commentWasAutoAdded = false;
this.sectionProperties.contentText.unedited = null;
this.textAreaInput();
this.onSaveComment(e);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public onSaveComment (e: any): void {
L.DomEvent.stopPropagation(e);
this.sectionProperties.data.text = this.sectionProperties.nodeModifyText.value;
this.updateContent();
if (!cool.CommentSection.autoSavedComment)
this.show();
this.sectionProperties.commentListSection.save(this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public onLostFocus (e: any): void {
if (!this.sectionProperties.isRemoved) {
$(this.sectionProperties.container).removeClass('annotation-active reply-annotation-container modify-annotation-container');
if (this.sectionProperties.contentText.origText !== this.sectionProperties.nodeModifyText.value) {
if (!this.sectionProperties.contentText.unedited)
this.sectionProperties.contentText.unedited = this.sectionProperties.contentText.origText;
cool.CommentSection.autoSavedComment = this;
this.onSaveComment(e);
}
else if (this.containerObject.testing) {
var insertButton = document.getElementById('menu-insertcomment');
if (insertButton) {
if (window.getComputedStyle(insertButton).display === 'none') {
this.onCancelClick(e);
}
}
}
}
app.view.commentHasFocus = false;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public onLostFocusReply (e: any): void {
if (this.sectionProperties.nodeReplyText.value !== '') {
if (!this.sectionProperties.contentText.unedited)
this.sectionProperties.contentText.unedited = this.sectionProperties.contentText.origText;
cool.CommentSection.autoSavedComment = this;
this.onReplyClick(e);
}
else {
this.sectionProperties.nodeReply.style.display = 'none';
}
}
public focus (): void {
this.sectionProperties.container.classList.add('annotation-active');
this.sectionProperties.nodeModifyText.focus();
this.sectionProperties.nodeReplyText.focus();
}
public reply (): Comment {
this.sectionProperties.container.classList.add('reply-annotation-container');
this.sectionProperties.container.style.visibility = '';
this.sectionProperties.contentNode.style.display = '';
this.sectionProperties.nodeModify.style.display = 'none';
this.sectionProperties.nodeReply.style.display = '';
return this;
}
public edit (): Comment {
this.doPendingInitializationInView(true /* force */);
this.sectionProperties.container.classList.add('modify-annotation-container');
this.sectionProperties.nodeModify.style.display = '';
this.sectionProperties.nodeReply.style.display = 'none';
this.sectionProperties.container.style.visibility = '';
this.sectionProperties.contentNode.style.display = 'none';
return this;
}
public isEdit (): boolean {
return !this.pendingInit && ((this.sectionProperties.nodeModify && this.sectionProperties.nodeModify.style.display !== 'none') ||
(this.sectionProperties.nodeReply && this.sectionProperties.nodeReply.style.display !== 'none'));
}
public static isAnyEdit (): boolean {
var section = app.sectionContainer && app.sectionContainer instanceof CanvasSectionContainer ?
app.sectionContainer.getSectionWithName(L.CSections.CommentList.name) : null;
if (!section) {
return false;
}
var commentList = section.sectionProperties.commentList;
for (var i in commentList) {
var modifyNode = commentList[i].sectionProperties.nodeModify;
var replyNode = commentList[i].sectionProperties.nodeReply;
if (!commentList[i].pendingInit &&
((modifyNode && modifyNode.style.display !== 'none') ||
(replyNode && replyNode.style.display !== 'none')))
return true;
}
return false;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private sendAnnotationPositionChange (newPosition: any): void {
if (app.file.fileBasedView) {
this.map.setPart(this.sectionProperties.docLayer._selectedPart, false);
newPosition.y -= this.sectionProperties.data.yAddition;
}
var comment = {
Id: {
type: 'string',
value: this.sectionProperties.data.id
},
PositionX: {
type: 'int32',
value: newPosition.x
},
PositionY: {
type: 'int32',
value: newPosition.y
}
};
this.map.sendUnoCommand('.uno:EditAnnotation', comment);
if (app.file.fileBasedView)
this.map.setPart(0, false);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private onMarkerDrag (event: any): void {
if (this.sectionProperties.annotationMarker == null)
return;
if (event.type === 'dragend') {
var pointTwip = this.sectionProperties.docLayer._latLngToTwips(this.sectionProperties.annotationMarker.getLatLng());
this.sendAnnotationPositionChange(pointTwip);
}
}
public isDisplayed (): boolean {
return (this.sectionProperties.container.style && this.sectionProperties.container.style.visibility === '');
}
public onResize (): void {
this.updatePosition();
}
private isSelected(): boolean {
return this.sectionProperties.commentListSection.sectionProperties.selectedComment === this;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
private doesRectangleContainPoint (rectangle: any, point: Array<number>): boolean {
if (point[0] >= rectangle[0] && point[0] <= rectangle[0] + rectangle[2]) {
if (point[1] >= rectangle[1] && point[1] <= rectangle[1] + rectangle[3]) {
return true;
}
}
return false;
}
/*
point is the core pixel coordinate of the cursor.
Not adjusted according to the view.
For adjusting, we need to take document top left and documentAnchor top left into account.
No need to do that for now.
*/
private checkIfCursorIsOnThisCommentWriter(rectangles: any, point: Array<number>) {
for (var i: number = 0; i < rectangles.length; i++) {
if (this.doesRectangleContainPoint(rectangles[i], point)) {
if (!this.isSelected()) {
this.sectionProperties.commentListSection.selectById(this.sectionProperties.data.id);
}
this.stopPropagating();
return;
}
}
// If we are here, this comment is not selected.
if (this.isSelected()) {
if (this.isCollapsed)
this.setCollapsed();
this.sectionProperties.commentListSection.unselect();
}
}
/// This event is Writer-only. Fired by CanvasSectionContainer.
public onCursorPositionChanged(newPosition: cool.SimpleRectangle): void {
var x = newPosition.pX1;
var y = Math.round(newPosition.pCenter[1]);
if (this.sectionProperties.pixelBasedOrgRectangles) {
this.checkIfCursorIsOnThisCommentWriter(this.sectionProperties.pixelBasedOrgRectangles, [x, y]);
}
}
/// This event is Calc-only. Fired by CanvasSectionContainer.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public onCellAddressChanged(): void {
if (this.sectionProperties.data.rectangles) {
var midX = this.containerObject.getDocumentAnchor()[0] + Math.round(app.calc.cellCursorRectangle.pCenter[0]);
var midY = this.containerObject.getDocumentAnchor()[1] + Math.round(app.calc.cellCursorRectangle.pCenter[1]);
if (midX > this.sectionProperties.data.rectangles[0][0] && midX < this.sectionProperties.data.rectangles[0][0] + this.sectionProperties.data.rectangles[0][2]
&& midY > this.sectionProperties.data.rectangles[0][1] && midY < this.sectionProperties.data.rectangles[0][1] + this.sectionProperties.data.rectangles[0][3]) {
this.sectionProperties.commentListSection.sectionProperties.calcCurrentComment = this;
}
else if (this.isSelected()) {
this.hide();
this.sectionProperties.commentListSection.sectionProperties.calcCurrentComment = null;
}
else if (this.sectionProperties.commentListSection.sectionProperties.calcCurrentComment == this)
this.sectionProperties.commentListSection.sectionProperties.calcCurrentComment = null;
}
}
public onClick (point: Array<number>, e: MouseEvent): void {
if (this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') {
this.sectionProperties.commentListSection.selectById(this.sectionProperties.data.id);
e.stopPropagation();
this.stopPropagating();
}
}
public onDraw (): void {
if (this.sectionProperties.showSelectedCoordinate) {
if (this.sectionProperties.docLayer._docType === 'text') {
var rectangles: Array<any> = this.sectionProperties.data.rectangles;
if (rectangles) {
this.context.fillStyle = this.sectionProperties.usedTextColor;
this.context.globalAlpha = 0.25;
for (var i: number = 0; i < this.sectionProperties.data.rectangles.length;i ++) {
var x = rectangles[i][0] - this.myTopLeft[0];
var y = rectangles[i][1] - this.myTopLeft[1];
var w = rectangles[i][2] > 3 ? rectangles[i][2]: 3;
var h = rectangles[i][3];
this.context.fillRect(x, y, w , h);
}
this.context.globalAlpha = 1;
}
}
else if (this.sectionProperties.docLayer._docType === 'spreadsheet' &&
parseInt(this.sectionProperties.data.tab) === this.sectionProperties.docLayer._selectedPart) {
var cellSize = this.calcCellSize();
if (cellSize[0] !== 0 && cellSize[1] !== 0) { // don't draw notes in hidden cells
// For calc comments (aka postits) draw the same sort of square as ScOutputData::DrawNoteMarks
// does for offline
var margin = 3;
var squareDim = 6;
// this.size may currently have an artifically wide size if mouseEnter without moveLeave seen
// so fetch the real size
var x = this.isCalcRTL() ? margin : cellSize[0] - (margin + squareDim);
this.context.fillStyle = '#FF0000';
this.context.fillRect(x, 0, squareDim, squareDim);
}
}
}
}
public onMouseMove (point: Array<number>, dragDistance: Array<number>, e: MouseEvent): void {
return;
}
public onMouseUp (point: Array<number>, e: MouseEvent): void {
// Hammer.js doesn't fire onClick event after touchEnd event.
// CanvasSectionContainer fires the onClick event. But since Hammer.js is used for map, it disables the onClick for SectionContainer.
// We will use this event as click event on touch devices, until we remove Hammer.js (then this code will be removed from here).
// Control.ColumnHeader.js file is not affected by this situation, because map element (so Hammer.js) doesn't cover headers.
if (!this.containerObject.isDraggingSomething() && (<any>window).mode.isMobile() || (<any>window).mode.isTablet()) {
if (this.sectionProperties.docLayer._docType === 'presentataion' || this.sectionProperties.docLayer._docType === 'drawing')
this.sectionProperties.docLayer._openCommentWizard(this);
this.onMouseEnter();
this.onClick(point, e);
}
}
public onMouseDown (point: Array<number>, e: MouseEvent): void {
return;
}
private calcContinueWithMouseEvent (): boolean {
if (this.sectionProperties.docLayer._docType === 'spreadsheet') {
var conditions: boolean = !this.isEdit();
if (conditions) {
var sc = this.sectionProperties.commentListSection.sectionProperties.selectedComment;
if (sc)
conditions = sc.sectionProperties.data.id !== 'new';
}
return conditions;
}
else {
return false;
}
}
public calcCellSize (): number[] {
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
var cellPos = this.map._docLayer._cellRangeToTwipRect(this.sectionProperties.data.cellRange).toRectangle();
return [Math.round((cellPos[2]) * ratio), Math.round((cellPos[3]) * ratio)];
}
public onMouseEnter (): void {
if (this.calcContinueWithMouseEvent()) {
// When mouse is above this section, comment's HTML element will be shown.
// If mouse pointer goes to HTML element, onMouseLeave event shouldn't be fired.
// But mouse pointer will have left the borders of this section and onMouseLeave event will be fired.
// Let's do it properly, when mouse is above this section, we will make this section's size bigger and onMouseLeave event will not be fired.
if (parseInt(this.sectionProperties.data.tab) === this.sectionProperties.docLayer._selectedPart) {
var sc = this.sectionProperties.commentListSection.sectionProperties.selectedComment;
if (sc) {
if (!sc.isEdit())
sc.hide();
else
return; // Another comment is being edited. Return.
}
var containerWidth: number = this.sectionProperties.container.getBoundingClientRect().width;
var ratio: number = (app.tile.size.pixels[0] / app.tile.size.twips[0]);
var cellPos = this.map._docLayer._cellRangeToTwipRect(this.sectionProperties.data.cellRange).toRectangle();
this.size = [Math.round((cellPos[2]) * ratio + containerWidth), Math.round((cellPos[3]) * ratio)];
this.sectionProperties.commentListSection.selectById(this.sectionProperties.data.id);
this.show();
}
}
}
public onMouseLeave (point: Array<number>): void {
if (this.calcContinueWithMouseEvent()) {
if (parseInt(this.sectionProperties.data.tab) === this.sectionProperties.docLayer._selectedPart) {
// Revert the changes we did on "onMouseEnter" event.
this.size = this.calcCellSize();
if (point) {
this.hide();
}
}
}
}
public onNewDocumentTopLeft (): void {
this.doPendingInitializationInView();
this.updatePosition();
}
public onCommentDataUpdate(): void {
this.doPendingInitializationInView();
this.updatePosition();
}
public onRemove (): void {
this.sectionProperties.isRemoved = true;
if (this.sectionProperties.commentListSection.sectionProperties.selectedComment === this)
this.sectionProperties.commentListSection.sectionProperties.selectedComment = null;
this.sectionProperties.commentListSection.hideArrow();
var container = this.sectionProperties.container;
this.hideMarker();
if (container && container.parentElement) {
var c: number = 0;
while (c < 10) {
try {
container.parentElement.removeChild(container);
break;
}
catch (e) {
c++;
}
}
}
}
public isRootComment(): boolean {
return this.sectionProperties.data.parent === '0';
}
public setAsRootComment(): void {
this.sectionProperties.data.parent = '0';
if (this.sectionProperties.docLayer._docType === 'text')
this.sectionProperties.data.parentId = '0';
}
public getChildrenLength(): number {
return this.sectionProperties.children.length;
}
public getChildByIndex(index: number): Comment {
if (this.sectionProperties.children.length > index)
return this.sectionProperties.children[index];
else
return null;
}
public removeChildByIndex(index: number): void {
if (this.sectionProperties.children.length > index)
this.sectionProperties.children.splice(index, 1);
}
public getParentCommentId(): string {
if (this.sectionProperties.data.parent && this.sectionProperties.data.parent !== '0')
return this.sectionProperties.data.parent;
else
return null;
}
public getIndexOfChild(comment: Comment): number {
return this.sectionProperties.children.indexOf(comment);
}
public setCollapsed(): void {
this.isCollapsed = true;
if (!this.isEdit())
this.show();
if (this.isRootComment() || this.sectionProperties.docLayer._docType === 'presentation' || this.sectionProperties.docLayer._docType === 'drawing') {
this.sectionProperties.container.style.display = '';
this.sectionProperties.container.style.visibility = 'hidden';
}
this.updateThreadInfoIndicator();
if (this.sectionProperties.data.resolved === 'false'
|| this.sectionProperties.commentListSection.sectionProperties.showResolved
|| this.sectionProperties.docLayer._docType === 'presentation'
|| this.sectionProperties.docLayer._docType === 'drawing')
L.DomUtil.addClass(this.sectionProperties.container, 'cool-annotation-collapsed-show');
}
public updateThreadInfoIndicator(replycount:number | string = -1): void {
if (this.sectionProperties.docLayer._docType === 'spreadsheet')
return;
if (this.isEdit())
this.sectionProperties.collapsedInfoNode.innerText = '!';
else if (replycount === '!' || typeof replycount === "number" && replycount > 0)
this.sectionProperties.collapsedInfoNode.innerText = replycount;
else
this.sectionProperties.collapsedInfoNode.innerText = '';
if (this.sectionProperties.collapsedInfoNode.innerText === '' || this.isContainerVisible())
this.sectionProperties.collapsedInfoNode.style.display = 'none';
else if ((!this.isContainerVisible() && this.sectionProperties.collapsedInfoNode.innerText !== ''))
this.sectionProperties.collapsedInfoNode.style.display = '';
}
public setExpanded(): void {
if (!this.isCollapsed)
return;
this.isCollapsed = false;
if (this.sectionProperties.data.resolved === 'false' || this.sectionProperties.commentListSection.sectionProperties.showResolved) {
this.sectionProperties.container.style.display = '';
this.sectionProperties.container.style.visibility = '';
}
if (this.sectionProperties.docLayer._docType === 'text')
this.sectionProperties.collapsedInfoNode.style.display = 'none';
L.DomUtil.removeClass(this.sectionProperties.container, 'cool-annotation-collapsed-show');
}
}
}
app.definitions.Comment = cool.Comment;