366 lines
7.6 KiB
TypeScript
366 lines
7.6 KiB
TypeScript
/**
|
|
* @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,
|
|
};
|
|
}
|