KEXEL/git
0
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>Advanced PDF Editor</title>7 <script src="https://cdn.tailwindcss.com"></script>8 <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>9 <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>10 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">11 <style>12 .pdf-container {13 position: relative;14 overflow: auto;15 border: 1px solid #e5e7eb;16 box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);17 }18 .canvas-container {19 margin: 0 auto;20 text-align: center;21 position: relative;22 }23 .annotation {24 position: absolute;25 border: 2px dashed #3b82f6;26 background-color: rgba(59, 130, 246, 0.2);27 cursor: move;28 }29 .annotation.selected {30 border: 2px solid #ef4444;31 z-index: 100;32 }33 .annotation-text {34 width: 100%;35 height: 100%;36 padding: 5px;37 resize: none;38 background: transparent;39 border: none;40 outline: none;41 font-family: Arial, sans-serif;42 }43 .tooltip {44 position: absolute;45 background: #1f2937;46 color: white;47 padding: 4px 8px;48 border-radius: 4px;49 font-size: 12px;50 opacity: 0;51 transition: opacity 0.2s;52 pointer-events: none;53 z-index: 1000;54 }55 .tooltip.active {56 opacity: 0.9;57 }58 @keyframes pulse {59 0%, 100% { opacity: 1; }60 50% { opacity: 0.5; }61 }62 .pulse {63 animation: pulse 1.5s infinite;64 }65 </style>66</head>67<body class="bg-gray-50 min-h-screen">68 <div class="container mx-auto px-4 py-8">69 <header class="mb-8">70 <h1 class="text-3xl font-bold text-gray-800 flex items-center">71 <i class="fas fa-file-pdf text-red-500 mr-3"></i>72 Advanced PDF Editor73 </h1>74 <p class="text-gray-600 mt-2">Upload, annotate, and modify your PDF documents with ease</p>75 </header>76 77 <div class="grid grid-cols-1 lg:grid-cols-4 gap-6">78 <!-- Tools Panel -->79 <div class="bg-white rounded-lg shadow p-4 lg:col-span-1">80 <h2 class="text-lg font-semibold text-gray-700 mb-4 flex items-center">81 <i class="fas fa-tools mr-2"></i> Tools82 </h2>83 84 <div class="space-y-4">85 <div>86 <label class="block text-sm font-medium text-gray-700 mb-1">Upload PDF</label>87 <div class="relative">88 <input type="file" id="pdf-upload" accept=".pdf" class="hidden">89 <label for="pdf-upload" class="w-full bg-blue-50 hover:bg-blue-100 text-blue-700 py-2 px-4 rounded border border-blue-200 flex items-center justify-center cursor-pointer transition">90 <i class="fas fa-cloud-upload-alt mr-2"></i>91 Choose File92 </label>93 <div id="file-name" class="text-xs text-gray-500 mt-1 truncate"></div>94 </div>95 </div>96 97 <div class="pt-2 border-t border-gray-200">98 <label class="block text-sm font-medium text-gray-700 mb-2">Annotation Tools</label>99 <div class="grid grid-cols-3 gap-2">100 <button id="text-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn active" data-tool="text">101 <i class="fas fa-font mb-1"></i>102 <span class="text-xs">Text</span>103 </button>104 <button id="highlight-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="highlight">105 <i class="fas fa-highlighter mb-1"></i>106 <span class="text-xs">Highlight</span>107 </button>108 <button id="rectangle-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="rectangle">109 <i class="fas fa-square mb-1"></i>110 <span class="text-xs">Rectangle</span>111 </button>112 <button id="freehand-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="freehand">113 <i class="fas fa-pen mb-1"></i>114 <span class="text-xs">Freehand</span>115 </button>116 <button id="stamp-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="stamp">117 <i class="fas fa-stamp mb-1"></i>118 <span class="text-xs">Stamp</span>119 </button>120 <button id="eraser-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="eraser">121 <i class="fas fa-eraser mb-1"></i>122 <span class="text-xs">Eraser</span>123 </button>124 </div>125 </div>126 127 <div class="pt-2 border-t border-gray-200">128 <label class="block text-sm font-medium text-gray-700 mb-2">Properties</label>129 <div class="space-y-3">130 <div>131 <label class="block text-xs text-gray-600 mb-1">Color</label>132 <input type="color" id="color-picker" value="#3b82f6" class="w-full h-8 cursor-pointer">133 </div>134 <div>135 <label class="block text-xs text-gray-600 mb-1">Opacity</label>136 <input type="range" id="opacity-slider" min="10" max="100" value="50" class="w-full">137 </div>138 <div>139 <label class="block text-xs text-gray-600 mb-1">Font Size</label>140 <select id="font-size" class="w-full border border-gray-200 rounded p-1 text-sm">141 <option value="12">12px</option>142 <option value="14">14px</option>143 <option value="16" selected>16px</option>144 <option value="18">18px</option>145 <option value="20">20px</option>146 <option value="24">24px</option>147 </select>148 </div>149 </div>150 </div>151 152 <div class="pt-2 border-t border-gray-200">153 <label class="block text-sm font-medium text-gray-700 mb-2">Pages</label>154 <div id="page-thumbnails" class="space-y-2 max-h-40 overflow-y-auto">155 <div class="text-center text-gray-500 py-4">156 <i class="fas fa-file-upload text-2xl mb-2"></i>157 <p class="text-sm">Upload a PDF to view pages</p>158 </div>159 </div>160 </div>161 </div>162 </div>163 164 <!-- Main Editor Area -->165 <div class="bg-white rounded-lg shadow p-4 lg:col-span-3">166 <div class="flex justify-between items-center mb-4">167 <h2 class="text-lg font-semibold text-gray-700 flex items-center">168 <i class="fas fa-edit mr-2"></i> Editor169 </h2>170 <div class="flex space-x-2">171 <button id="zoom-in" class="bg-gray-100 hover:bg-gray-200 text-gray-800 p-2 rounded">172 <i class="fas fa-search-plus"></i>173 </button>174 <button id="zoom-out" class="bg-gray-100 hover:bg-gray-200 text-gray-800 p-2 rounded">175 <i class="fas fa-search-minus"></i>176 </button>177 <button id="download-pdf" class="bg-blue-50 hover:bg-blue-100 text-blue-700 py-2 px-4 rounded border border-blue-200 flex items-center">178 <i class="fas fa-file-download mr-2"></i>179 Download180 </button>181 </div>182 </div>183 184 <div id="pdf-container" class="pdf-container h-[70vh] bg-gray-100 rounded-lg flex items-center justify-center">185 <div id="pdf-placeholder" class="text-center p-8">186 <i class="fas fa-file-pdf text-5xl text-gray-300 mb-4"></i>187 <h3 class="text-xl font-medium text-gray-500 mb-2">No PDF Loaded</h3>188 <p class="text-gray-400 mb-4">Upload a PDF file to start editing</p>189 <label for="pdf-upload" class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-6 rounded cursor-pointer inline-block">190 <i class="fas fa-cloud-upload-alt mr-2"></i>191 Select PDF File192 </label>193 </div>194 <div id="pdf-viewer" class="hidden w-full h-full relative overflow-auto">195 <div id="canvas-container" class="canvas-container"></div>196 </div>197 </div>198 199 <div class="mt-4 flex justify-between items-center">200 <div class="flex items-center space-x-4">201 <button id="prev-page" class="bg-gray-100 hover:bg-gray-200 text-gray-800 p-2 rounded disabled:opacity-50" disabled>202 <i class="fas fa-chevron-left"></i>203 </button>204 <span id="page-info" class="text-sm text-gray-600">Page: 0/0</span>205 <button id="next-page" class="bg-gray-100 hover:bg-gray-200 text-gray-800 p-2 rounded disabled:opacity-50" disabled>206 <i class="fas fa-chevron-right"></i>207 </button>208 </div>209 <div class="text-sm text-gray-500">210 <span id="zoom-level">100%</span>211 </div>212 </div>213 </div>214 </div>215 216 <div class="mt-8 bg-white rounded-lg shadow p-4">217 <h2 class="text-lg font-semibold text-gray-700 mb-4 flex items-center">218 <i class="fas fa-history mr-2"></i> Recent Actions219 </h2>220 <div id="action-history" class="text-sm text-gray-600">221 <div class="text-center text-gray-400 py-4">222 <i class="fas fa-info-circle mr-1"></i>223 No actions recorded yet224 </div>225 </div>226 </div>227 </div>228 229 <div id="tooltip" class="tooltip"></div>230 231 <script>232 // Initialize PDF.js233 pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';234 235 // Variables236 let pdfDoc = null;237 let pageNum = 1;238 let pageRendering = false;239 let pageNumPending = null;240 let scale = 1.0;241 let canvas = null;242 let ctx = null;243 let currentTool = 'text';244 let annotations = [];245 let selectedAnnotation = null;246 let isDragging = false;247 let startX, startY;248 let currentPage = 1;249 let pdfName = '';250 let actionHistory = [];251 252 // DOM Elements253 const pdfUpload = document.getElementById('pdf-upload');254 const pdfViewer = document.getElementById('pdf-viewer');255 const pdfPlaceholder = document.getElementById('pdf-placeholder');256 const canvasContainer = document.getElementById('canvas-container');257 const pageInfo = document.getElementById('page-info');258 const prevPage = document.getElementById('prev-page');259 const nextPage = document.getElementById('next-page');260 const zoomIn = document.getElementById('zoom-in');261 const zoomOut = document.getElementById('zoom-out');262 const zoomLevel = document.getElementById('zoom-level');263 const downloadPdf = document.getElementById('download-pdf');264 const toolButtons = document.querySelectorAll('.tool-btn');265 const colorPicker = document.getElementById('color-picker');266 const opacitySlider = document.getElementById('opacity-slider');267 const fontSizeSelect = document.getElementById('font-size');268 const pageThumbnails = document.getElementById('page-thumbnails');269 const actionHistoryContainer = document.getElementById('action-history');270 const fileNameDisplay = document.getElementById('file-name');271 const tooltip = document.getElementById('tooltip');272 273 // Event Listeners274 pdfUpload.addEventListener('change', handleFileSelect);275 prevPage.addEventListener('click', onPrevPage);276 nextPage.addEventListener('click', onNextPage);277 zoomIn.addEventListener('click', () => changeZoom(0.2));278 zoomOut.addEventListener('click', () => changeZoom(-0.2));279 downloadPdf.addEventListener('click', downloadModifiedPdf);280 281 // Tool buttons282 toolButtons.forEach(button => {283 button.addEventListener('click', () => {284 toolButtons.forEach(btn => btn.classList.remove('active', 'bg-blue-100', 'border-blue-300'));285 button.classList.add('active', 'bg-blue-100', 'border-blue-300');286 currentTool = button.dataset.tool;287 addActionToHistory(`Switched to ${button.dataset.tool} tool`);288 });289 });290 291 // Initialize first tool as active292 document.getElementById('text-tool').classList.add('active', 'bg-blue-100', 'border-blue-300');293 294 // Functions295 function handleFileSelect(event) {296 const file = event.target.files[0];297 if (file && file.type === 'application/pdf') {298 pdfName = file.name;299 fileNameDisplay.textContent = file.name;300 301 const fileReader = new FileReader();302 fileReader.onload = function() {303 const typedArray = new Uint8Array(this.result);304 loadPdf(typedArray);305 };306 fileReader.readAsArrayBuffer(file);307 308 addActionToHistory(`Uploaded PDF: ${file.name}`);309 } else {310 alert('Please select a valid PDF file.');311 }312 }313 314 function loadPdf(data) {315 pdfjsLib.getDocument(data).promise.then(function(pdfDoc_) {316 pdfDoc = pdfDoc_;317 pageInfo.textContent = `Page: 1/${pdfDoc.numPages}`;318 319 // Enable/disable buttons320 prevPage.disabled = true;321 nextPage.disabled = pdfDoc.numPages <= 1;322 323 // Show PDF viewer and hide placeholder324 pdfViewer.classList.remove('hidden');325 pdfPlaceholder.classList.add('hidden');326 327 // Render first page328 renderPage(1);329 330 // Generate thumbnails331 generateThumbnails();332 }).catch(function(error) {333 console.error('Error loading PDF:', error);334 alert('Error loading PDF. Please try another file.');335 });336 }337 338 function renderPage(num) {339 pageRendering = true;340 currentPage = num;341 342 // Update page display343 pageInfo.textContent = `Page: ${num}/${pdfDoc.numPages}`;344 345 // Update button states346 prevPage.disabled = num <= 1;347 nextPage.disabled = num >= pdfDoc.numPages;348 349 // Clear previous canvas350 canvasContainer.innerHTML = '';351 352 // Create new canvas353 canvas = document.createElement('canvas');354 canvas.className = 'pdf-page';355 canvasContainer.appendChild(canvas);356 ctx = canvas.getContext('2d');357 358 // Get page359 pdfDoc.getPage(num).then(function(page) {360 const viewport = page.getViewport({ scale: scale });361 canvas.height = viewport.height;362 canvas.width = viewport.width;363 364 // Render PDF page into canvas context365 const renderContext = {366 canvasContext: ctx,367 viewport: viewport368 };369 370 const renderTask = page.render(renderContext);371 372 renderTask.promise.then(function() {373 pageRendering = false;374 if (pageNumPending !== null) {375 renderPage(pageNumPending);376 pageNumPending = null;377 }378 379 // Render annotations for this page380 renderAnnotations();381 });382 });383 384 // Highlight current page in thumbnails385 highlightCurrentThumbnail(num);386 }387 388 function onPrevPage() {389 if (pageNum <= 1) {390 return;391 }392 pageNum--;393 queueRenderPage(pageNum);394 addActionToHistory(`Navigated to page ${pageNum}`);395 }396 397 function onNextPage() {398 if (pageNum >= pdfDoc.numPages) {399 return;400 }401 pageNum++;402 queueRenderPage(pageNum);403 addActionToHistory(`Navigated to page ${pageNum}`);404 }405 406 function queueRenderPage(num) {407 if (pageRendering) {408 pageNumPending = num;409 } else {410 renderPage(num);411 }412 }413 414 function changeZoom(delta) {415 const newScale = scale + delta;416 if (newScale >= 0.2 && newScale <= 3.0) {417 scale = newScale;418 zoomLevel.textContent = `${Math.round(scale * 100)}%`;419 queueRenderPage(pageNum);420 addActionToHistory(`Zoom changed to ${Math.round(scale * 100)}%`);421 }422 }423 424 function generateThumbnails() {425 pageThumbnails.innerHTML = '';426 427 for (let i = 1; i <= pdfDoc.numPages; i++) {428 const thumbContainer = document.createElement('div');429 thumbContainer.className = 'relative cursor-pointer group';430 thumbContainer.dataset.page = i;431 432 const thumbCanvas = document.createElement('canvas');433 thumbCanvas.className = 'border border-gray-200 w-full';434 435 const pageNum = document.createElement('div');436 pageNum.className = 'absolute bottom-1 right-1 bg-black bg-opacity-50 text-white text-xs px-1 rounded';437 pageNum.textContent = i;438 439 thumbContainer.appendChild(thumbCanvas);440 thumbContainer.appendChild(pageNum);441 442 // Render thumbnail443 pdfDoc.getPage(i).then(function(page) {444 const viewport = page.getViewport(0.2);445 thumbCanvas.height = viewport.height;446 thumbCanvas.width = viewport.width;447 448 page.render({449 canvasContext: thumbCanvas.getContext('2d'),450 viewport: viewport451 });452 });453 454 // Click event to navigate to page455 thumbContainer.addEventListener('click', function() {456 pageNum = parseInt(this.dataset.page);457 queueRenderPage(pageNum);458 addActionToHistory(`Navigated to page ${pageNum} via thumbnail`);459 });460 461 // Hover effect462 thumbContainer.addEventListener('mouseenter', function() {463 this.classList.add('ring-2', 'ring-blue-400');464 });465 466 thumbContainer.addEventListener('mouseleave', function() {467 if (parseInt(this.dataset.page) !== currentPage) {468 this.classList.remove('ring-2', 'ring-blue-400');469 }470 });471 472 pageThumbnails.appendChild(thumbContainer);473 }474 }475 476 function highlightCurrentThumbnail(pageNum) {477 const thumbs = pageThumbnails.querySelectorAll('[data-page]');478 thumbs.forEach(thumb => {479 if (parseInt(thumb.dataset.page) === pageNum) {480 thumb.classList.add('ring-2', 'ring-blue-500');481 } else {482 thumb.classList.remove('ring-2', 'ring-blue-500');483 }484 });485 }486 487 // Annotation functions488 function renderAnnotations() {489 // Clear existing annotations490 const existingAnnotations = canvasContainer.querySelectorAll('.annotation');491 existingAnnotations.forEach(ann => ann.remove());492 493 // Filter annotations for current page494 const pageAnnotations = annotations.filter(ann => ann.page === currentPage);495 496 // Render each annotation497 pageAnnotations.forEach(annotation => {498 const annElement = document.createElement('div');499 annElement.className = 'annotation';500 annElement.dataset.id = annotation.id;501 502 // Position and size503 annElement.style.left = `${annotation.x}px`;504 annElement.style.top = `${annotation.y}px`;505 annElement.style.width = `${annotation.width}px`;506 annElement.style.height = `${annotation.height}px`;507 508 // Style based on type509 if (annotation.type === 'text') {510 const textarea = document.createElement('textarea');511 textarea.className = 'annotation-text';512 textarea.value = annotation.content || '';513 textarea.style.color = annotation.color;514 textarea.style.fontSize = `${annotation.fontSize}px`;515 textarea.style.opacity = annotation.opacity / 100;516 517 // Update annotation content when text changes518 textarea.addEventListener('input', function() {519 const ann = annotations.find(a => a.id === annotation.id);520 if (ann) ann.content = this.value;521 });522 523 annElement.appendChild(textarea);524 } else if (annotation.type === 'highlight') {525 annElement.style.backgroundColor = annotation.color;526 annElement.style.opacity = annotation.opacity / 100;527 } else if (annotation.type === 'rectangle') {528 annElement.style.border = `2px solid ${annotation.color}`;529 annElement.style.backgroundColor = 'transparent';530 }531 532 // Selection533 if (selectedAnnotation && selectedAnnotation.id === annotation.id) {534 annElement.classList.add('selected');535 }536 537 // Event listeners for interaction538 annElement.addEventListener('mousedown', startAnnotationDrag);539 annElement.addEventListener('mouseup', stopAnnotationDrag);540 annElement.addEventListener('dblclick', deleteAnnotation);541 542 canvasContainer.appendChild(annElement);543 });544 }545 546 function createAnnotation(e) {547 if (!canvas || currentTool === 'eraser') return;548 549 const rect = canvas.getBoundingClientRect();550 const x = e.clientX - rect.left;551 const y = e.clientY - rect.top;552 553 const newAnnotation = {554 id: Date.now().toString(),555 type: currentTool,556 page: currentPage,557 x: x,558 y: y,559 width: currentTool === 'text' ? 200 : 100,560 height: currentTool === 'text' ? 50 : 30,561 color: colorPicker.value,562 opacity: opacitySlider.value,563 fontSize: fontSizeSelect.value,564 content: ''565 };566 567 annotations.push(newAnnotation);568 selectedAnnotation = newAnnotation;569 570 renderAnnotations();571 572 // Focus textarea if it's a text annotation573 if (currentTool === 'text') {574 setTimeout(() => {575 const textarea = canvasContainer.querySelector('.annotation.selected textarea');576 if (textarea) textarea.focus();577 }, 10);578 }579 580 addActionToHistory(`Added ${currentTool} annotation`);581 }582 583 function startAnnotationDrag(e) {584 if (e.target.tagName === 'TEXTAREA') return;585 586 e.preventDefault();587 e.stopPropagation();588 589 const annotationId = e.currentTarget.dataset.id;590 selectedAnnotation = annotations.find(ann => ann.id === annotationId);591 592 // Update all annotations to show selected state593 renderAnnotations();594 595 // Start drag596 isDragging = true;597 startX = e.clientX;598 startY = e.clientY;599 600 document.addEventListener('mousemove', dragAnnotation);601 document.addEventListener('mouseup', stopAnnotationDrag);602 }603 604 function dragAnnotation(e) {605 if (!isDragging || !selectedAnnotation) return;606 607 const rect = canvas.getBoundingClientRect();608 const dx = e.clientX - startX;609 const dy = e.clientY - startY;610 611 selectedAnnotation.x += dx;612 selectedAnnotation.y += dy;613 614 startX = e.clientX;615 startY = e.clientY;616 617 renderAnnotations();618 }619 620 function stopAnnotationDrag() {621 isDragging = false;622 document.removeEventListener('mousemove', dragAnnotation);623 document.removeEventListener('mouseup', stopAnnotationDrag);624 625 if (selectedAnnotation) {626 addActionToHistory(`Moved ${selectedAnnotation.type} annotation`);627 }628 }629 630 function deleteAnnotation(e) {631 e.stopPropagation();632 633 const annotationId = e.currentTarget.dataset.id;634 const annotationIndex = annotations.findIndex(ann => ann.id === annotationId);635 636 if (annotationIndex !== -1) {637 const deletedAnnotation = annotations[annotationIndex];638 annotations.splice(annotationIndex, 1);639 selectedAnnotation = null;640 641 renderAnnotations();642 addActionToHistory(`Deleted ${deletedAnnotation.type} annotation`);643 }644 }645 646 // Canvas click handler for creating new annotations647 canvasContainer.addEventListener('mousedown', function(e) {648 if (e.target === canvasContainer || e.target === canvas) {649 createAnnotation(e);650 }651 });652 653 // Tooltip handling654 document.querySelectorAll('[data-tooltip]').forEach(el => {655 el.addEventListener('mouseenter', function() {656 const tooltipText = this.dataset.tooltip;657 tooltip.textContent = tooltipText;658 tooltip.classList.add('active');659 660 const rect = this.getBoundingClientRect();661 tooltip.style.left = `${rect.left + rect.width / 2 - tooltip.offsetWidth / 2}px`;662 tooltip.style.top = `${rect.top - tooltip.offsetHeight - 5}px`;663 });664 665 el.addEventListener('mouseleave', function() {666 tooltip.classList.remove('active');667 });668 });669 670 // Download modified PDF671 function downloadModifiedPdf() {672 if (!pdfDoc) {673 alert('No PDF loaded');674 return;675 }676 677 addActionToHistory('Initiated PDF download');678 679 // In a real implementation, you would use jsPDF or similar to create a new PDF680 // with the annotations. This is a simplified version that just alerts.681 682 alert('In a full implementation, this would download the modified PDF with all annotations.');683 684 // For demonstration, we'll create a simple download of the original file685 if (pdfUpload.files.length > 0) {686 const file = pdfUpload.files[0];687 const url = URL.createObjectURL(file);688 689 const a = document.createElement('a');690 a.href = url;691 a.download = `modified_${file.name}`;692 document.body.appendChild(a);693 a.click();694 695 setTimeout(() => {696 document.body.removeChild(a);697 URL.revokeObjectURL(url);698 }, 0);699 }700 }701 702 // Action history703 function addActionToHistory(action) {704 const timestamp = new Date().toLocaleTimeString();705 actionHistory.unshift({ action, timestamp });706 707 if (actionHistory.length > 10) {708 actionHistory.pop();709 }710 711 updateActionHistoryDisplay();712 }713 714 function updateActionHistoryDisplay() {715 if (actionHistory.length === 0) {716 actionHistoryContainer.innerHTML = `717 <div class="text-center text-gray-400 py-4">718 <i class="fas fa-info-circle mr-1"></i>719 No actions recorded yet720 </div>721 `;722 return;723 }724 725 actionHistoryContainer.innerHTML = '';726 727 actionHistory.forEach(item => {728 const actionElement = document.createElement('div');729 actionElement.className = 'flex justify-between items-center py-2 border-b border-gray-100';730 731 const actionText = document.createElement('span');732 actionText.textContent = item.action;733 actionText.className = 'text-gray-700';734 735 const actionTime = document.createElement('span');736 actionTime.textContent = item.timestamp;737 actionTime.className = 'text-gray-500 text-xs';738 739 actionElement.appendChild(actionText);740 actionElement.appendChild(actionTime);741 actionHistoryContainer.appendChild(actionElement);742 });743 }744 </script>745</body>746</html>