From a0d47623432cd146e3a7016848c4f446488580eb Mon Sep 17 00:00:00 2001 From: Keep Creating Online Date: Fri, 3 Jan 2025 17:18:45 -0500 Subject: [PATCH] update --- src/app.js | 150 +++++++++++++++++++++++ src/script.js | 11 +- src/styles.css | 102 ++++++++++++---- src/view-archive.html | 272 ++---------------------------------------- 4 files changed, 245 insertions(+), 290 deletions(-) create mode 100644 src/app.js diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..3d5a874 --- /dev/null +++ b/src/app.js @@ -0,0 +1,150 @@ +// Global variables +let archiveData = []; +let sortedData = []; +let currentSort = { column: 'created_at', order: 'desc' }; +const rowsPerPage = 100; +let currentPage = 1; + +// Event Listeners +document.addEventListener('DOMContentLoaded', function() { + document.getElementById('fileInput').addEventListener('change', handleFileLoad); + + // Add click listeners to sortable headers + document.querySelectorAll('#dataTable th[data-sort]').forEach(th => { + th.addEventListener('click', () => { + sortTable(th.dataset.sort); + }); + }); +}); + +// File handling +function handleFileLoad(event) { + const file = event.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = JSON.parse(e.target.result); + if (!Array.isArray(data)) throw new Error('Invalid archive format: Expected an array.'); + archiveData = data; + sortedData = [...archiveData]; + updateStats(); + sortTable(currentSort.column); // Initial sort + } catch (err) { + alert(`Failed to load archive: ${err.message}`); + } + }; + reader.readAsText(file); +} + +// Statistics +function updateStats() { + const statsDiv = document.getElementById('stats'); + const kindCounts = archiveData.reduce((acc, event) => { + acc[event.kind] = (acc[event.kind] || 0) + 1; + return acc; + }, {}); + + const totalEvents = archiveData.length; + let statsText = `Total Events: ${totalEvents}
`; + statsText += 'Most Common Types: '; + + const topKinds = Object.entries(kindCounts) + .sort(([,a], [,b]) => b - a) + .slice(0, 3) + .map(([kind, count]) => `${getKindLabel(parseInt(kind))}: ${count}`); + + statsText += topKinds.join(' | '); + + statsDiv.innerHTML = statsText; +} + +// Table rendering +function renderTable() { + const tbody = document.querySelector('#dataTable tbody'); + tbody.innerHTML = ''; + + const start = (currentPage - 1) * rowsPerPage; + const end = start + rowsPerPage; + const pageData = sortedData.slice(start, end); + + if (pageData.length === 0) { + tbody.innerHTML = ` + No data loaded. Please load a JSON archive file. + `; + return; + } + + pageData.forEach((event, index) => { + const row = document.createElement('tr'); + row.innerHTML = ` + ${start + index + 1} + ${new Date(event.created_at * 1000).toLocaleString()} + ${getKindLabel(event.kind)} + ${getContentDisplay(event)} + `; + tbody.appendChild(row); + }); + + updatePagination(end); +} + +// Pagination +function updatePagination(end) { + document.getElementById('prevPage').disabled = currentPage === 1; + document.getElementById('nextPage').disabled = end >= sortedData.length; + document.getElementById('pageInfo').innerText = `Page ${currentPage} of ${Math.ceil(sortedData.length / rowsPerPage)}`; +} + +function changePage(offset) { + currentPage += offset; + renderTable(); +} + +// Sorting +function sortTable(column) { + if (currentSort.column === column) { + currentSort.order = currentSort.order === 'asc' ? 'desc' : 'asc'; + } else { + currentSort.column = column; + currentSort.order = 'desc'; + } + + const order = currentSort.order === 'asc' ? 1 : -1; + sortedData.sort((a, b) => { + if (a[column] < b[column]) return -1 * order; + if (a[column] > b[column]) return 1 * order; + return 0; + }); + + currentPage = 1; + renderTable(); +} + +// Helper functions +function getKindLabel(kind) { + const kinds = { + 0: 'Profile Metadata', + 1: 'Short Text Note', + 2: 'Recommend Relay', + 3: 'Contacts', + 4: 'Encrypted DM', + 6: 'Repost', + 7: 'Reaction', + 40: 'Channel Creation', + 41: 'Channel Metadata', + 42: 'Channel Message', + 30023: 'Long-form Content', + // Add more as needed + }; + return `${kind} (${kinds[kind] || 'Unknown'})`; +} + +function getContentDisplay(event) { + if (event.kind === 4) { + const recipient = event.tags.find(tag => tag[0] === 'p'); + return `Encrypted Message: ${event.content || 'N/A'}
Recipient: ${recipient ? recipient[1] : 'Unknown'}`; + } + return event.content || 'No content available'; +} \ No newline at end of file diff --git a/src/script.js b/src/script.js index caf8c93..dc0fce3 100644 --- a/src/script.js +++ b/src/script.js @@ -52,11 +52,10 @@ document.addEventListener('DOMContentLoaded', () => { // Update the filter to include additional kinds const filter = { - kinds: [0, 1, 2, 3, 4, 6, 7, 10002, 30023], // Include multiple event kinds - authors: [pubkey], - limit: 1000, + kinds: [0, 1, 2, 3, 4, 6, 7, 10002, 30023, 10509], // Include all relevant kinds + authors: [pubkey], // Fetch events from the specified pubkey }; - + relayUrls.forEach((url) => { try { const sub = pool.sub([url], [filter]); @@ -78,12 +77,16 @@ document.addEventListener('DOMContentLoaded', () => { case 4: console.log('Encrypted DM captured:', event); break; + case 10509: + console.log('Ephemeral DM captured:', event); + break; case 30023: console.log('Long-Form Content captured:', event); break; default: console.log('Other event captured:', event); } + collectedEvents.push(event); // Store the raw event data eventIds.add(event.id); } diff --git a/src/styles.css b/src/styles.css index d19d9db..a4aeef2 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,50 +1,110 @@ +/* Base styles */ body { font-family: Arial, sans-serif; - background-color: #f4f4f4; margin: 0; padding: 0; + background-color: #f9f9f9; } .container { - width: 90%; - max-width: 600px; - margin: 50px auto; - background: #fff; + max-width: 1200px; + margin: 20px auto; padding: 20px; + background: #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } +/* Header and Stats */ h1 { text-align: center; + margin-bottom: 10px; } -label { +.stats { + text-align: center; + color: #666; + margin-bottom: 20px; + font-size: 1.1em; +} + +input[type="file"] { display: block; - margin-top: 15px; + margin: 20px auto; } -input[type="text"], -textarea { +/* Table styles */ +table { width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +th, td { padding: 10px; - margin-top: 5px; - box-sizing: border-box; + border: 1px solid #ddd; + text-align: left; + vertical-align: top; } -button { - margin-top: 20px; - padding: 10px 15px; - background-color: #007BFF; - border: none; - color: #fff; +th { cursor: pointer; + background: #f4f4f4; + position: relative; } -button:hover { - background-color: #0056b3; +th:hover { + background: #e0e0e0; } -#status { +.empty-message { + text-align: center; + color: #888; margin-top: 20px; - min-height: 20px; } + +.content-cell { + max-width: 600px; + white-space: pre-wrap; + word-break: break-word; +} + +/* Sort indicators */ +.sort-indicator::after { + content: '⬍'; + margin-left: 5px; +} + +.sort-asc::after { + content: '↑'; +} + +.sort-desc::after { + content: '↓'; +} + +.number-column { + width: 50px; + text-align: right; + color: #666; +} + +/* Pagination */ +.pagination { + text-align: center; + margin: 20px 0; +} + +.pagination button { + margin: 0 5px; + padding: 5px 10px; + background: #007BFF; + color: white; + border: none; + cursor: pointer; + border-radius: 3px; +} + +.pagination button:disabled { + background: #ccc; + cursor: not-allowed; +} \ No newline at end of file diff --git a/src/view-archive.html b/src/view-archive.html index 4e2a8f4..c894c16 100644 --- a/src/view-archive.html +++ b/src/view-archive.html @@ -4,79 +4,7 @@ Nostr Archive Viewer - +
@@ -98,198 +26,12 @@ +
- - + \ No newline at end of file