UNPKG

sourabhrealtime

Version:

ROBUST RICH TEXT EDITOR: Single-pane contentEditable with direct text selection formatting, speech features, undo/redo, professional UI - Perfect TipTap alternative

180 lines (152 loc) 4.71 kB
// Persistent storage for cross-browser collaboration export class PersistentStorage { constructor() { this.storageKey = 'saas_collab_data'; this.init(); } init() { if (typeof localStorage !== 'undefined') { const stored = localStorage.getItem(this.storageKey); this.data = stored ? JSON.parse(stored) : { projects: {}, invitations: {}, users: {}, sessions: {}, activities: {} }; } else { this.data = { projects: {}, invitations: {}, users: {}, sessions: {}, activities: {} }; } } save() { if (typeof localStorage !== 'undefined') { localStorage.setItem(this.storageKey, JSON.stringify(this.data)); } } // Project management saveProject(project) { this.data.projects[project.id] = { ...project, lastSaved: Date.now() }; this.save(); } getProject(projectId) { return this.data.projects[projectId]; } getAllProjects() { return Object.values(this.data.projects); } // User sessions saveUserSession(userId, sessionData) { this.data.sessions[userId] = { ...sessionData, lastActive: Date.now(), browser: this.getBrowserInfo() }; this.save(); } getUserSession(userId) { return this.data.sessions[userId]; } getAllActiveSessions() { const now = Date.now(); const activeThreshold = 5 * 60 * 1000; // 5 minutes return Object.entries(this.data.sessions) .filter(([_, session]) => now - session.lastActive < activeThreshold) .map(([userId, session]) => ({ userId, ...session })); } // Invitations saveInvitation(invitation) { if (!this.data.invitations[invitation.invitedUserId]) { this.data.invitations[invitation.invitedUserId] = []; } this.data.invitations[invitation.invitedUserId].push(invitation); this.save(); } getUserInvitations(userId) { return this.data.invitations[userId] || []; } updateInvitationStatus(userId, invitationId, status) { const userInvitations = this.data.invitations[userId] || []; const invitation = userInvitations.find(inv => inv.id === invitationId); if (invitation) { invitation.status = status; invitation.updatedAt = Date.now(); this.save(); } } // Activity tracking logActivity(projectId, userId, action, data = {}) { if (!this.data.activities[projectId]) { this.data.activities[projectId] = []; } this.data.activities[projectId].unshift({ id: 'act_' + Math.random().toString(36).substr(2, 9), userId, action, data, timestamp: Date.now(), browser: this.getBrowserInfo() }); // Keep only last 100 activities per project if (this.data.activities[projectId].length > 100) { this.data.activities[projectId] = this.data.activities[projectId].slice(0, 100); } this.save(); } getProjectActivities(projectId, limit = 20) { return (this.data.activities[projectId] || []).slice(0, limit); } // Browser detection getBrowserInfo() { if (typeof navigator === 'undefined') return 'Unknown'; const userAgent = navigator.userAgent; let browser = 'Unknown'; if (userAgent.includes('Chrome') && !userAgent.includes('Edg')) { browser = 'Chrome'; } else if (userAgent.includes('Firefox')) { browser = 'Firefox'; } else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) { browser = 'Safari'; } else if (userAgent.includes('Edg')) { browser = 'Edge'; } return browser; } // Cross-browser sync syncData(remoteData) { // Merge remote data with local data Object.keys(remoteData.projects || {}).forEach(projectId => { const remoteProject = remoteData.projects[projectId]; const localProject = this.data.projects[projectId]; if (!localProject || remoteProject.lastSaved > localProject.lastSaved) { this.data.projects[projectId] = remoteProject; } }); this.save(); } // Clear old data cleanup() { const now = Date.now(); const oldThreshold = 24 * 60 * 60 * 1000; // 24 hours // Clean old sessions Object.keys(this.data.sessions).forEach(userId => { if (now - this.data.sessions[userId].lastActive > oldThreshold) { delete this.data.sessions[userId]; } }); // Clean old activities Object.keys(this.data.activities).forEach(projectId => { this.data.activities[projectId] = this.data.activities[projectId] .filter(activity => now - activity.timestamp < oldThreshold); }); this.save(); } }