nickp 3 mesi fa
parent
commit
b92183bb66
3 ha cambiato i file con 192 aggiunte e 17 eliminazioni
  1. 24 3
      .eleventy.js
  2. 1 1
      package.json
  3. 167 13
      src/analyses.njk

+ 24 - 3
.eleventy.js

@@ -298,18 +298,39 @@ module.exports = function(eleventyConfig) {
           : firstPage.document_metadata?.date
       };
 
-      return {
+      // Create lightweight pages array (keep full_text but make them lazy)
+      const lightPages = docPages.map(p => {
+        const lightPage = { ...p };
+        // Keep full_text reference for document rendering, but it won't be duplicated
+        return lightPage;
+      });
+
+      // Only include full_text when needed (for individual document pages)
+      // For the main documents array, we skip it to save memory
+      const docData = {
         unique_id: normalizedDocNum,  // Normalized version for unique URLs
         document_number: rawDocNums.length === 1 ? rawDocNums[0] : normalizedDocNum, // Show original if consistent, else normalized
         raw_document_numbers: rawDocNums, // All variations found
-        pages: docPages,
+        pages: lightPages,
         page_count: docPages.length,
         document_metadata: normalizedMetadata,
         entities: deduplicatedEntities,
-        full_text: docPages.map(p => p.full_text).join('\n\n--- PAGE BREAK ---\n\n'),
         folder: folders.join(', '),  // Show all folders if document spans multiple
         folders: folders  // Keep array for reference
       };
+
+      // Add full_text getter that loads on demand
+      Object.defineProperty(docData, 'full_text', {
+        get: function() {
+          if (!this._full_text) {
+            this._full_text = this.pages.map(p => p.full_text).join('\n\n--- PAGE BREAK ---\n\n');
+          }
+          return this._full_text;
+        },
+        enumerable: true
+      });
+
+      return docData;
     });
 
     cachedDocuments = documents;

+ 1 - 1
package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "description": "Static site for exploring OCR'd documents",
   "scripts": {
-    "build": "eleventy",
+    "build": "NODE_OPTIONS='--max-old-space-size=8192' eleventy",
     "start": "eleventy --serve"
   },
   "devDependencies": {

+ 167 - 13
src/analyses.njk

@@ -4,7 +4,7 @@ title: Document Analyses
 ---
 
 <h1>Document Analyses</h1>
-<p class="subtitle">AI-generated summaries for {{ analyses.length }} documents</p>
+<p class="subtitle">AI-generated summaries for <span id="total-count">{{ analyses.length }}</span> documents</p>
 
 <div class="search-box">
   <input type="text" id="search" placeholder="Search analyses...">
@@ -17,6 +17,22 @@ title: Document Analyses
   {% endfor %}
 </div>
 
+<div class="pagination-controls">
+  <button id="prev-page" disabled>← Previous</button>
+  <span id="page-info">Page 1</span>
+  <button id="next-page">Next →</button>
+  <span class="page-size-selector">
+    Show:
+    <select id="page-size">
+      <option value="50">50</option>
+      <option value="100" selected>100</option>
+      <option value="250">250</option>
+      <option value="500">500</option>
+    </select>
+    per page
+  </span>
+</div>
+
 <div class="table-container">
   <table class="analyses-table">
     <thead>
@@ -29,7 +45,7 @@ title: Document Analyses
     </thead>
     <tbody id="results">
       {% for item in analyses %}
-      <tr class="analysis-row" data-content="{{ [item.document_number, item.analysis.document_type, item.analysis.summary, item.analysis.significance, item.analysis.key_topics | join(' ')] | join(' ') | lower }}" data-type="{{ item.analysis.document_type | lower }}">
+      <tr class="analysis-row" data-type="{{ item.analysis.document_type | lower }}">
         <td class="doc-number">
           <a href="/document/{{ item.document_id | slugify }}/">{{ item.document_number }}</a>
         </td>
@@ -75,6 +91,56 @@ title: Document Analyses
     color: white;
   }
 
+  .pagination-controls {
+    display: flex;
+    align-items: center;
+    gap: 1rem;
+    margin-bottom: 1.5rem;
+    padding: 1rem;
+    background: white;
+    border: 1px solid #e0e0e0;
+    border-radius: 6px;
+  }
+
+  .pagination-controls button {
+    padding: 0.5rem 1rem;
+    background: white;
+    border: 1px solid #e0e0e0;
+    border-radius: 4px;
+    cursor: pointer;
+    font-size: 0.875rem;
+    transition: all 0.2s;
+  }
+
+  .pagination-controls button:hover:not(:disabled) {
+    border-color: #3498db;
+    color: #3498db;
+  }
+
+  .pagination-controls button:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+
+  #page-info {
+    font-size: 0.875rem;
+    color: #666;
+    margin: 0 0.5rem;
+  }
+
+  .page-size-selector {
+    margin-left: auto;
+    font-size: 0.875rem;
+    color: #666;
+  }
+
+  .page-size-selector select {
+    padding: 0.25rem 0.5rem;
+    border: 1px solid #e0e0e0;
+    border-radius: 4px;
+    margin: 0 0.5rem;
+  }
+
   .table-container {
     overflow-x: auto;
     background: white;
@@ -188,7 +254,33 @@ title: Document Analyses
     }
   }
 
+  @media (max-width: 900px) {
+    .pagination-controls {
+      flex-wrap: wrap;
+    }
+
+    .page-size-selector {
+      margin-left: 0;
+      width: 100%;
+      text-align: center;
+    }
+  }
+
   @media (max-width: 600px) {
+    .pagination-controls {
+      padding: 0.75rem;
+      gap: 0.5rem;
+    }
+
+    .pagination-controls button {
+      font-size: 0.8rem;
+      padding: 0.4rem 0.75rem;
+    }
+
+    #page-info {
+      font-size: 0.75rem;
+    }
+
     /* Stack table on mobile */
     .analyses-table thead {
       display: none;
@@ -251,35 +343,97 @@ title: Document Analyses
   const results = document.getElementById('results');
   const allRows = Array.from(results.querySelectorAll('.analysis-row'));
   const filterButtons = document.querySelectorAll('.filter-btn');
+  const prevButton = document.getElementById('prev-page');
+  const nextButton = document.getElementById('next-page');
+  const pageInfo = document.getElementById('page-info');
+  const pageSizeSelect = document.getElementById('page-size');
+  const totalCount = document.getElementById('total-count');
 
   let currentFilter = 'all';
   let currentSearch = '';
-
-  // Filter function that applies both type filter and search
-  function applyFilters() {
-    allRows.forEach(row => {
+  let currentPage = 1;
+  let pageSize = 100;
+  let filteredRows = allRows;
+
+  // Filter and pagination function
+  function applyFiltersAndPagination() {
+    // First, filter rows based on type and search
+    filteredRows = allRows.filter(row => {
       const matchesType = currentFilter === 'all' || row.dataset.type === currentFilter;
-      const matchesSearch = currentSearch === '' || row.dataset.content.includes(currentSearch);
-      row.style.display = (matchesType && matchesSearch) ? '' : 'none';
+
+      let matchesSearch = true;
+      if (currentSearch !== '') {
+        const rowText = row.textContent.toLowerCase();
+        matchesSearch = rowText.includes(currentSearch);
+      }
+
+      return matchesType && matchesSearch;
     });
+
+    // Update total count
+    totalCount.textContent = filteredRows.length;
+
+    // Calculate pagination
+    const totalPages = Math.ceil(filteredRows.length / pageSize);
+    const startIndex = (currentPage - 1) * pageSize;
+    const endIndex = startIndex + pageSize;
+
+    // Hide all rows first
+    allRows.forEach(row => row.style.display = 'none');
+
+    // Show only current page rows
+    filteredRows.slice(startIndex, endIndex).forEach(row => {
+      row.style.display = '';
+    });
+
+    // Update pagination controls
+    pageInfo.textContent = `Page ${currentPage} of ${totalPages || 1} (${filteredRows.length} total)`;
+    prevButton.disabled = currentPage <= 1;
+    nextButton.disabled = currentPage >= totalPages;
   }
 
   // Search handler
   search.addEventListener('input', (e) => {
     currentSearch = e.target.value.toLowerCase().trim();
-    applyFilters();
+    currentPage = 1; // Reset to first page on search
+    applyFiltersAndPagination();
   });
 
   // Filter button handlers
   filterButtons.forEach(btn => {
     btn.addEventListener('click', () => {
-      // Update active state
       filterButtons.forEach(b => b.classList.remove('active'));
       btn.classList.add('active');
-
-      // Update filter and apply
       currentFilter = btn.dataset.type;
-      applyFilters();
+      currentPage = 1; // Reset to first page on filter change
+      applyFiltersAndPagination();
     });
   });
+
+  // Pagination handlers
+  prevButton.addEventListener('click', () => {
+    if (currentPage > 1) {
+      currentPage--;
+      applyFiltersAndPagination();
+      window.scrollTo({ top: 0, behavior: 'smooth' });
+    }
+  });
+
+  nextButton.addEventListener('click', () => {
+    const totalPages = Math.ceil(filteredRows.length / pageSize);
+    if (currentPage < totalPages) {
+      currentPage++;
+      applyFiltersAndPagination();
+      window.scrollTo({ top: 0, behavior: 'smooth' });
+    }
+  });
+
+  pageSizeSelect.addEventListener('change', (e) => {
+    pageSize = parseInt(e.target.value);
+    currentPage = 1; // Reset to first page on page size change
+    applyFiltersAndPagination();
+  });
+
+  // Initial render
+  applyFiltersAndPagination();
 </script>