// Enhanced Document Viewer with Editing and Commenting
(function() {
'use strict';
class DocumentEditor {
constructor() {
this.comments = {};
this.editMode = false;
this.currentDocId = null;
this.annotations = {};
}
enableEditMode(docId) {
this.editMode = true;
this.currentDocId = docId;
const contentArea = document.getElementById('document-content');
const content = contentArea.querySelector('.document-content');
if (content) {
content.contentEditable = true;
content.style.border = '2px dashed #ff4757';
content.style.padding = '20px';
// Add edit toolbar
this.createEditToolbar(contentArea);
// Enable text selection for commenting
this.enableTextSelection(content);
}
}
createEditToolbar(container) {
const toolbar = document.createElement('div');
toolbar.className = 'document-edit-toolbar';
toolbar.style.cssText = `
position: sticky;
top: 0;
background: #2c3e50;
padding: 10px;
display: flex;
gap: 10px;
z-index: 150;
border-radius: 4px;
margin-bottom: 10px;
`;
toolbar.innerHTML = `
`;
// Style buttons
toolbar.querySelectorAll('.edit-btn').forEach(btn => {
btn.style.cssText = `
padding: 6px 12px;
background: #34495e;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
`;
btn.addEventListener('mouseover', () => {
btn.style.background = '#ff4757';
});
btn.addEventListener('mouseout', () => {
btn.style.background = '#34495e';
});
btn.addEventListener('click', (e) => {
this.handleToolbarAction(e.currentTarget.dataset.action);
});
});
container.insertBefore(toolbar, container.firstChild);
}
handleToolbarAction(action) {
const content = document.querySelector('.document-content[contenteditable="true"]');
switch(action) {
case 'bold':
document.execCommand('bold', false, null);
break;
case 'italic':
document.execCommand('italic', false, null);
break;
case 'underline':
document.execCommand('underline', false, null);
break;
case 'highlight':
this.highlightSelection();
break;
case 'comment':
this.addCommentToSelection();
break;
case 'save':
this.saveDocument();
break;
case 'export':
this.exportToPDF();
break;
case 'exit':
this.exitEditMode();
break;
}
}
highlightSelection() {
const selection = window.getSelection();
if (selection.toString()) {
const range = selection.getRangeAt(0);
const span = document.createElement('span');
span.style.backgroundColor = '#fff59d';
span.className = 'document-highlight';
try {
range.surroundContents(span);
} catch (e) {
// Handle complex selections
const contents = range.extractContents();
span.appendChild(contents);
range.insertNode(span);
}
selection.removeAllRanges();
}
}
addCommentToSelection() {
const selection = window.getSelection();
if (selection.toString()) {
const commentText = prompt('Add your comment:');
if (commentText) {
const commentId = 'comment_' + Date.now();
const range = selection.getRangeAt(0);
// Create comment marker
const marker = document.createElement('span');
marker.className = 'comment-marker';
marker.dataset.commentId = commentId;
marker.style.cssText = `
background: #ffe082;
border-bottom: 2px solid #ff9800;
cursor: pointer;
position: relative;
`;
try {
range.surroundContents(marker);
} catch (e) {
const contents = range.extractContents();
marker.appendChild(contents);
range.insertNode(marker);
}
// Store comment
this.comments[commentId] = {
text: commentText,
author: 'Current User',
timestamp: new Date().toISOString(),
position: marker.getBoundingClientRect()
};
// Add click handler to show comment
marker.addEventListener('click', () => {
this.showComment(commentId);
});
// Show comment count badge
this.updateCommentCount();
selection.removeAllRanges();
}
} else {
alert('Please select text to comment on');
}
}
showComment(commentId) {
const comment = this.comments[commentId];
if (comment) {
// Remove existing comment popup
const existingPopup = document.querySelector('.comment-popup');
if (existingPopup) {
existingPopup.remove();
}
// Create comment popup
const popup = document.createElement('div');
popup.className = 'comment-popup';
popup.style.cssText = `
position: absolute;
background: white;
border: 1px solid #ccc;
border-radius: 8px;
padding: 15px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 200;
max-width: 300px;
right: 20px;
top: ${comment.position.top}px;
`;
popup.innerHTML = `
${comment.author}
${comment.text}
${new Date(comment.timestamp).toLocaleString()}
`;
document.getElementById('document-viewer').appendChild(popup);
}
}
updateCommentCount() {
const count = Object.keys(this.comments).length;
let badge = document.querySelector('.comment-count-badge');
if (!badge) {
badge = document.createElement('div');
badge.className = 'comment-count-badge';
badge.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
background: #ff9800;
color: white;
padding: 8px 12px;
border-radius: 20px;
font-weight: bold;
z-index: 150;
`;
document.getElementById('document-viewer').appendChild(badge);
}
badge.textContent = `💬 ${count} Comments`;
}
saveDocument() {
const content = document.querySelector('.document-content[contenteditable="true"]');
if (content) {
const savedContent = content.innerHTML;
const docId = this.currentDocId || 'doc_' + Date.now();
// Save to localStorage (in real app, would save to backend)
const savedDoc = {
id: docId,
content: savedContent,
comments: this.comments,
annotations: this.annotations,
lastModified: new Date().toISOString(),
version: 1
};
localStorage.setItem(`navada_doc_${docId}`, JSON.stringify(savedDoc));
// Show success message
this.showNotification('Document saved successfully!', 'success');
return savedDoc;
}
}
exportToPDF() {
const content = document.querySelector('.document-content[contenteditable="true"]');
if (content) {
// In a real implementation, this would generate a proper PDF
// For now, we'll trigger a print dialog which can save as PDF
const printWindow = window.open('', '_blank');
printWindow.document.write(`
Document Export
${content.innerHTML}
`);
printWindow.document.close();
printWindow.print();
}
}
exitEditMode() {
const content = document.querySelector('.document-content[contenteditable="true"]');
if (content) {
content.contentEditable = false;
content.style.border = 'none';
}
// Remove toolbar
const toolbar = document.querySelector('.document-edit-toolbar');
if (toolbar) {
toolbar.remove();
}
this.editMode = false;
this.showNotification('Edit mode disabled', 'info');
}
enableTextSelection(element) {
element.addEventListener('mouseup', () => {
const selection = window.getSelection();
if (selection.toString()) {
this.showSelectionMenu(selection);
}
});
}
showSelectionMenu(selection) {
// Remove existing menu
const existingMenu = document.querySelector('.selection-menu');
if (existingMenu) {
existingMenu.remove();
}
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
const menu = document.createElement('div');
menu.className = 'selection-menu';
menu.style.cssText = `
position: fixed;
top: ${rect.top - 40}px;
left: ${rect.left + (rect.width / 2) - 100}px;
background: #2c3e50;
border-radius: 4px;
padding: 5px;
display: flex;
gap: 5px;
z-index: 300;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
`;
const actions = [
{ icon: '💬', action: 'comment', title: 'Comment' },
{ icon: '🖍️', action: 'highlight', title: 'Highlight' },
{ icon: '📝', action: 'note', title: 'Add Note' }
];
actions.forEach(item => {
const btn = document.createElement('button');
btn.textContent = item.icon;
btn.title = item.title;
btn.style.cssText = `
background: transparent;
border: none;
color: white;
cursor: pointer;
padding: 5px 10px;
font-size: 16px;
`;
btn.addEventListener('click', () => {
if (item.action === 'comment') {
this.addCommentToSelection();
} else if (item.action === 'highlight') {
this.highlightSelection();
} else if (item.action === 'note') {
this.addNoteToSelection();
}
menu.remove();
});
menu.appendChild(btn);
});
document.body.appendChild(menu);
// Remove menu when clicking elsewhere
setTimeout(() => {
document.addEventListener('click', function removeMenu(e) {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener('click', removeMenu);
}
});
}, 100);
}
addNoteToSelection() {
const selection = window.getSelection();
if (selection.toString()) {
const noteText = prompt('Add your note:');
if (noteText) {
const noteId = 'note_' + Date.now();
const range = selection.getRangeAt(0);
// Create note marker
const marker = document.createElement('span');
marker.className = 'note-marker';
marker.dataset.noteId = noteId;
marker.style.cssText = `
background: #e1f5fe;
border-bottom: 2px solid #03a9f4;
cursor: help;
position: relative;
`;
marker.title = noteText;
try {
range.surroundContents(marker);
} catch (e) {
const contents = range.extractContents();
marker.appendChild(contents);
range.insertNode(marker);
}
// Store note
this.annotations[noteId] = {
type: 'note',
text: noteText,
timestamp: new Date().toISOString()
};
selection.removeAllRanges();
}
}
}
showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `document-notification ${type}`;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 8px;
color: white;
font-weight: 500;
z-index: 1000;
animation: slideIn 0.3s ease;
${type === 'success' ? 'background: #27ae60;' : ''}
${type === 'error' ? 'background: #e74c3c;' : ''}
${type === 'info' ? 'background: #3498db;' : ''}
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Collaborative features (simulation)
simulateCollaboration() {
// In a real app, this would use WebSockets
setInterval(() => {
const collaborators = ['Alice', 'Bob', 'Charlie'];
const randomUser = collaborators[Math.floor(Math.random() * collaborators.length)];
const cursor = document.createElement('div');
cursor.className = 'collaborator-cursor';
cursor.style.cssText = `
position: absolute;
width: 2px;
height: 20px;
background: #ff4757;
top: ${Math.random() * 400}px;
left: ${Math.random() * 600}px;
z-index: 100;
`;
const label = document.createElement('div');
label.textContent = randomUser;
label.style.cssText = `
position: absolute;
top: -20px;
left: 0;
background: #ff4757;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
white-space: nowrap;
`;
cursor.appendChild(label);
const viewer = document.getElementById('document-viewer');
if (viewer) {
viewer.appendChild(cursor);
// Remove after animation
setTimeout(() => cursor.remove(), 5000);
}
}, 10000);
}
}
// Initialize editor
window.DocumentEditor = new DocumentEditor();
// Add edit button to document viewer header
function addEditButton() {
const interval = setInterval(() => {
const header = document.querySelector('.document-header');
if (header && !header.querySelector('.edit-document-btn')) {
const editBtn = document.createElement('button');
editBtn.className = 'edit-document-btn';
editBtn.textContent = '✏️ Edit';
editBtn.style.cssText = `
padding: 6px 16px;
background: #27ae60;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
`;
editBtn.addEventListener('click', () => {
window.DocumentEditor.enableEditMode('current');
});
const btnContainer = header.querySelector('div');
if (btnContainer) {
btnContainer.insertBefore(editBtn, btnContainer.firstChild);
}
clearInterval(interval);
}
}, 1000);
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addEditButton);
} else {
setTimeout(addEditButton, 1000);
}
// Add styles for animations
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
.collaborator-cursor {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
`;
document.head.appendChild(style);
})();