sourabhrealtime
Version:
ROBUST RICH TEXT EDITOR: Single-pane contentEditable with direct text selection formatting, speech features, undo/redo, professional UI - Perfect TipTap alternative
281 lines (233 loc) • 8.07 kB
JavaScript
// Project management with tracking and collaboration
export class ProjectManager {
constructor() {
this.projects = new Map();
this.projectMembers = new Map();
this.projectInvitations = new Map();
this.projectActivity = new Map();
this.projectVersions = new Map();
}
// Create new project
createProject(projectData, creatorId) {
const project = {
id: 'proj_' + Math.random().toString(36).substr(2, 9),
name: projectData.name,
description: projectData.description,
createdBy: creatorId,
createdAt: Date.now(),
updatedAt: Date.now(),
status: 'active',
content: '<h1>Welcome to your new project!</h1><p>Start collaborating...</p>',
version: 1,
settings: {
allowComments: true,
allowEditing: true,
trackChanges: true,
autoSave: true
}
};
this.projects.set(project.id, project);
// Add creator as admin
this.addProjectMember(project.id, creatorId, 'admin');
// Initialize activity tracking
this.projectActivity.set(project.id, []);
this.projectVersions.set(project.id, [{
version: 1,
content: project.content,
createdBy: creatorId,
createdAt: Date.now(),
changes: 'Project created'
}]);
this.logActivity(project.id, creatorId, 'project_created', {
projectName: project.name
});
return project;
}
// Add member to project
addProjectMember(projectId, userId, role = 'editor') {
if (!this.projectMembers.has(projectId)) {
this.projectMembers.set(projectId, new Map());
}
const members = this.projectMembers.get(projectId);
members.set(userId, {
userId,
role,
joinedAt: Date.now(),
lastActivity: Date.now(),
permissions: this.getRolePermissions(role)
});
this.logActivity(projectId, userId, 'member_added', { role });
}
// Get role permissions
getRolePermissions(role) {
const permissions = {
admin: ['read', 'write', 'delete', 'invite', 'manage'],
editor: ['read', 'write', 'comment'],
viewer: ['read', 'comment'],
commenter: ['read', 'comment']
};
return permissions[role] || permissions.viewer;
}
// Send project invitation
sendInvitation(projectId, invitedUserId, invitedByUserId, role = 'editor') {
const project = this.projects.get(projectId);
if (!project) return null;
const invitation = {
id: 'inv_' + Math.random().toString(36).substr(2, 9),
projectId,
projectName: project.name,
invitedUserId,
invitedByUserId,
role,
status: 'pending',
createdAt: Date.now(),
expiresAt: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7 days
};
if (!this.projectInvitations.has(invitedUserId)) {
this.projectInvitations.set(invitedUserId, []);
}
this.projectInvitations.get(invitedUserId).push(invitation);
this.logActivity(projectId, invitedByUserId, 'invitation_sent', {
invitedUserId,
role
});
return invitation;
}
// Accept invitation
acceptInvitation(invitationId, userId) {
const userInvitations = this.projectInvitations.get(userId) || [];
const invitation = userInvitations.find(inv => inv.id === invitationId);
if (!invitation || invitation.status !== 'pending') {
return { success: false, message: 'Invalid invitation' };
}
if (invitation.expiresAt < Date.now()) {
return { success: false, message: 'Invitation expired' };
}
// Add user to project
this.addProjectMember(invitation.projectId, userId, invitation.role);
// Update invitation status
invitation.status = 'accepted';
invitation.acceptedAt = Date.now();
this.logActivity(invitation.projectId, userId, 'invitation_accepted', {
role: invitation.role
});
return { success: true, project: this.projects.get(invitation.projectId) };
}
// Update project content with version tracking
updateContent(projectId, userId, newContent, changes = '') {
const project = this.projects.get(projectId);
if (!project) return false;
const member = this.projectMembers.get(projectId)?.get(userId);
if (!member || !member.permissions.includes('write')) {
return false;
}
// Create new version
const newVersion = project.version + 1;
project.content = newContent;
project.version = newVersion;
project.updatedAt = Date.now();
// Store version history
const versions = this.projectVersions.get(projectId) || [];
versions.push({
version: newVersion,
content: newContent,
createdBy: userId,
createdAt: Date.now(),
changes: changes || 'Content updated'
});
this.projectVersions.set(projectId, versions);
// Update member activity
member.lastActivity = Date.now();
this.logActivity(projectId, userId, 'content_updated', {
version: newVersion,
changes
});
return true;
}
// Log project activity
logActivity(projectId, userId, action, metadata = {}) {
if (!this.projectActivity.has(projectId)) {
this.projectActivity.set(projectId, []);
}
const activity = {
id: 'act_' + Math.random().toString(36).substr(2, 9),
projectId,
userId,
action,
metadata,
timestamp: Date.now()
};
const activities = this.projectActivity.get(projectId);
activities.unshift(activity); // Add to beginning
// Keep only last 1000 activities
if (activities.length > 1000) {
activities.splice(1000);
}
}
// Get user's projects
getUserProjects(userId) {
const userProjects = [];
for (const [projectId, members] of this.projectMembers.entries()) {
if (members.has(userId)) {
const project = this.projects.get(projectId);
const member = members.get(userId);
if (project) {
userProjects.push({
...project,
userRole: member.role,
userPermissions: member.permissions,
memberCount: members.size,
lastActivity: member.lastActivity
});
}
}
}
return userProjects.sort((a, b) => b.updatedAt - a.updatedAt);
}
// Get user's invitations
getUserInvitations(userId) {
return this.projectInvitations.get(userId) || [];
}
// Get project activity
getProjectActivity(projectId, limit = 50) {
const activities = this.projectActivity.get(projectId) || [];
return activities.slice(0, limit);
}
// Get project members
getProjectMembers(projectId) {
const members = this.projectMembers.get(projectId);
if (!members) return [];
return Array.from(members.values());
}
// Get project versions
getProjectVersions(projectId, limit = 10) {
const versions = this.projectVersions.get(projectId) || [];
return versions.slice(-limit).reverse();
}
// Check user permission
hasPermission(projectId, userId, permission) {
const member = this.projectMembers.get(projectId)?.get(userId);
return member?.permissions.includes(permission) || false;
}
// Get project analytics
getProjectAnalytics(projectId) {
const project = this.projects.get(projectId);
const members = this.projectMembers.get(projectId);
const activities = this.projectActivity.get(projectId) || [];
const versions = this.projectVersions.get(projectId) || [];
if (!project || !members) return null;
const now = Date.now();
const dayMs = 24 * 60 * 60 * 1000;
const weekMs = 7 * dayMs;
return {
totalMembers: members.size,
totalVersions: versions.length,
totalActivities: activities.length,
activitiesLastWeek: activities.filter(a => now - a.timestamp < weekMs).length,
activitiesLastDay: activities.filter(a => now - a.timestamp < dayMs).length,
activeMembers: Array.from(members.values()).filter(m => now - m.lastActivity < weekMs).length,
lastUpdated: project.updatedAt,
createdAt: project.createdAt
};
}
}