// // Copyright © 2024 Hardcore Engineering Inc. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may // obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // // See the License for the specific language governing permissions and // limitations under the License. import { type Editor } from '@tiptap/core' import documents, { ControlledDocumentState, DocumentState, type EditorMode, type ControlledDocument, type DocumentReviewRequest, type DocumentSection } from '@hcengineering/controlled-documents' import { generateId, getCurrentAccount, type Ref } from '@hcengineering/core' import { type PersonAccount } from '@hcengineering/contact' import { RequestStatus } from '@hcengineering/request' import { getClient } from '@hcengineering/presentation' import { type ActionContext } from '@hcengineering/text-editor' import { getNodeElement, selectNode, nodeUuidName } from '@hcengineering/text-editor-resources' import { getCurrentEmployee } from './utils' import { showAddCommentPopupFx } from './stores/editors/document' import { $editorMode } from './stores/editors/document/editor' async function getCurrentReviewRequest (doc: ControlledDocument): Promise { const client = getClient() return await client.findOne(documents.class.DocumentReviewRequest, { attachedTo: doc._id, attachedToClass: doc._class, status: RequestStatus.Active }) } async function getDocumentStateForCurrentUser ( doc: ControlledDocument ): Promise { if (doc == null) { return null } const reviewRequest = await getCurrentReviewRequest(doc) if (doc.controlledState === ControlledDocumentState.InReview) { if (reviewRequest == null) { return ControlledDocumentState.InReview } const me = (getCurrentAccount() as PersonAccount).person if (reviewRequest.approved?.includes(me)) { return ControlledDocumentState.Reviewed } } if (doc.controlledState != null) { return doc.controlledState } return doc.state } function isDocumentOwner (doc: ControlledDocument): boolean { if (doc == null) { return false } const employee = getCurrentEmployee() return doc.owner === employee } function isDocumentCoAuthor (doc: ControlledDocument): boolean { if (doc == null) { return false } const employee = getCurrentEmployee() if (employee === undefined) { return false } return doc.coAuthors.includes(employee) } function isDocumentReviewer (doc: ControlledDocument): boolean { if (doc == null) { return false } const employee = getCurrentEmployee() if (employee == null) { return false } return doc.reviewers?.includes(employee) ?? false } function canViewDocumentComments (doc: ControlledDocument, mode: EditorMode): boolean { return ( (isDocumentOwner(doc) || isDocumentCoAuthor(doc) || isDocumentReviewer(doc)) && (mode === 'viewing' || mode === 'editing') ) } async function canAddDocumentComments (doc: ControlledDocument, mode: EditorMode): Promise { if (!canViewDocumentComments(doc, mode)) { return false } const state = await getDocumentStateForCurrentUser(doc) if (state === DocumentState.Draft) { return isDocumentOwner(doc) || isDocumentCoAuthor(doc) } if (state === ControlledDocumentState.InReview) { return isDocumentOwner(doc) || isDocumentCoAuthor(doc) || isDocumentReviewer(doc) } return false } function markNodeWithUuid (editor: Editor): string | undefined { if (editor === undefined) { return } const nodeId = generateId() editor.commands.setNodeUuid(nodeId) return nodeId } export async function comment (editor: Editor, event: MouseEvent, ctx: ActionContext): Promise { const { objectId, objectClass } = ctx if (objectId === undefined || objectClass === undefined) { return } const client = getClient() const section = (await client.findOne(objectClass, { _id: objectId })) as DocumentSection if (section === undefined) { return } let selectedNodeId = editor.extensionStorage[nodeUuidName].activeNodeUuid if (selectedNodeId == null) { selectedNodeId = markNodeWithUuid(editor) } if (selectedNodeId == null) { return } await showAddCommentPopupFx({ element: getNodeElement(editor, selectedNodeId), nodeId: selectedNodeId, sectionKey: section.key }) selectNode(editor, selectedNodeId) } export async function isCommentVisible (editor: Editor, ctx: ActionContext): Promise { const { objectId, objectClass } = ctx if (objectId === undefined || objectClass === undefined) { return false } const client = getClient() if (!client.getHierarchy().isDerived(objectClass, documents.class.DocumentSection)) { return false } const section = (await client.findOne(objectClass, { _id: objectId })) as DocumentSection if (section === undefined) { return false } const doc = await client.findOne(documents.class.ControlledDocument, { _id: section.attachedTo as Ref }) if (doc === undefined) { return false } // TODO: move editor mode tracking to a new extension! const mode = $editorMode.getState() return await canAddDocumentComments(doc, mode) }