/** * @file collaboration.ts * @description Collaboration interfaces * Comments, suggestions, user presence, and collaborative editing support */ import * as plugins from './plugins.js'; import type { TPresenceStatus, TCommentThreadStatus, TSuggestionStatus, TSuggestionChangeType, TConflictResolutionStrategy, TEditingMode, TCollaborationPermission, } from './types.js'; import type { IVersionChange } from './versioning.js'; // ============================================================================ // USER PRESENCE // ============================================================================ /** * User's current location in document */ export interface IUserLocation { paragraphId?: string; characterPosition?: number; selectionRange?: { start: number; end: number; }; } /** * Device information for presence */ export interface IDeviceInfo { type: 'desktop' | 'tablet' | 'mobile'; browser?: string; } /** * User presence information */ export interface IUserPresence { userId: string; displayName: string; avatarUrl?: string; color: string; status: TPresenceStatus; currentLocation?: IUserLocation; lastActivity: number; sessionStarted: number; deviceInfo?: IDeviceInfo; } /** * Real-time cursor position for collaborative editing */ export interface ICursorPosition { userId: string; paragraphId: string; offset: number; timestamp: number; } // ============================================================================ // COMMENTS // ============================================================================ /** * Anchor point for a comment */ export interface ICommentAnchor { type: 'paragraph' | 'text_range' | 'document' | 'party' | 'attachment'; paragraphId?: string; textRange?: { start: number; end: number; quotedText: string; }; elementPath?: string; } /** * User mention in comment */ export interface IMention { userId: string; displayName: string; notified: boolean; notifiedAt?: number; } /** * Reaction on a comment */ export interface IReaction { emoji: string; userId: string; timestamp: number; } /** * Individual comment within a thread */ export interface IComment { id: string; threadId: string; authorId: string; authorDisplayName: string; authorAvatarUrl?: string; content: string; createdAt: number; editedAt?: number; mentions: IMention[]; reactions: IReaction[]; parentCommentId?: string; isDeleted: boolean; } /** * Comment thread on a specific location */ export interface ICommentThread { id: string; contractId: string; versionId: string; anchor: ICommentAnchor; status: TCommentThreadStatus; resolvedBy?: string; resolvedAt?: number; comments: IComment[]; createdAt: number; createdBy: string; lastActivityAt: number; } // ============================================================================ // SUGGESTIONS // ============================================================================ /** * Suggestion for proposed changes (track changes mode) */ export interface ISuggestion { id: string; contractId: string; versionId: string; targetParagraphId: string; targetTextRange?: { start: number; end: number; }; changeType: TSuggestionChangeType; originalContent: string; suggestedContent: string; status: TSuggestionStatus; suggestedBy: string; suggestedByDisplayName: string; suggestedAt: number; reviewedBy?: string; reviewedAt?: number; rejectionReason?: string; discussionThreadId?: string; } // ============================================================================ // CONFLICTS // ============================================================================ /** * Conflicting change from a user */ export interface IConflictingChange { userId: string; userDisplayName: string; content: string; timestamp: number; } /** * Conflict marker for simultaneous edits */ export interface IEditConflict { id: string; contractId: string; paragraphId: string; baseContent: string; changes: IConflictingChange[]; status: 'unresolved' | 'resolved' | 'auto_merged'; resolvedContent?: string; resolvedBy?: string; resolvedAt?: number; resolutionStrategy?: TConflictResolutionStrategy; } // ============================================================================ // COLLABORATION SESSION // ============================================================================ /** * Collaborative editing session */ export interface ICollaborationSession { id: string; contractId: string; versionId: string; participants: IUserPresence[]; editingMode: TEditingMode; activeCursors: ICursorPosition[]; pendingChanges: IVersionChange[]; startedAt: number; lastActivityAt: number; autoSaveInterval: number; } // ============================================================================ // COLLABORATOR // ============================================================================ /** * Notification preferences for a collaborator */ export interface INotificationPreferences { onComment: boolean; onSuggestion: boolean; onVersionPublish: boolean; onSignatureRequest: boolean; } /** * Collaborator with permission */ export interface ICollaborator { userId: string; contact: plugins.tsclass.business.TContact; permission: TCollaborationPermission; invitedBy: string; invitedAt: number; acceptedAt?: number; expiresAt?: number; notificationPreferences: INotificationPreferences; } // ============================================================================ // FACTORY FUNCTIONS // ============================================================================ /** * Create a new comment thread */ export function createCommentThread( contractId: string, versionId: string, anchor: ICommentAnchor, userId: string ): ICommentThread { const now = Date.now(); return { id: crypto.randomUUID(), contractId, versionId, anchor, status: 'open', comments: [], createdAt: now, createdBy: userId, lastActivityAt: now, }; } /** * Create a new comment */ export function createComment( threadId: string, authorId: string, authorDisplayName: string, content: string ): IComment { return { id: crypto.randomUUID(), threadId, authorId, authorDisplayName, content, createdAt: Date.now(), mentions: [], reactions: [], isDeleted: false, }; } /** * Create a new suggestion */ export function createSuggestion( contractId: string, versionId: string, targetParagraphId: string, changeType: TSuggestionChangeType, originalContent: string, suggestedContent: string, suggestedBy: string, suggestedByDisplayName: string ): ISuggestion { return { id: crypto.randomUUID(), contractId, versionId, targetParagraphId, changeType, originalContent, suggestedContent, status: 'pending', suggestedBy, suggestedByDisplayName, suggestedAt: Date.now(), }; } /** * Create a new collaborator */ export function createCollaborator( userId: string, contact: plugins.tsclass.business.TContact, permission: TCollaborationPermission, invitedBy: string ): ICollaborator { return { userId, contact, permission, invitedBy, invitedAt: Date.now(), notificationPreferences: { onComment: true, onSuggestion: true, onVersionPublish: true, onSignatureRequest: true, }, }; } /** * Create default user presence */ export function createUserPresence( userId: string, displayName: string, color: string ): IUserPresence { const now = Date.now(); return { userId, displayName, color, status: 'viewing', lastActivity: now, sessionStarted: now, }; }