mirror of
https://github.com/PR0M3TH3AN/Archivestr.git
synced 2025-09-08 07:19:03 +00:00
update
This commit is contained in:
150
src/app.js
Normal file
150
src/app.js
Normal file
@@ -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}<br>`;
|
||||||
|
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 = `<tr class="empty-message">
|
||||||
|
<td colspan="4">No data loaded. Please load a JSON archive file.</td>
|
||||||
|
</tr>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageData.forEach((event, index) => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td class="number-column">${start + index + 1}</td>
|
||||||
|
<td>${new Date(event.created_at * 1000).toLocaleString()}</td>
|
||||||
|
<td>${getKindLabel(event.kind)}</td>
|
||||||
|
<td class="content-cell">${getContentDisplay(event)}</td>
|
||||||
|
`;
|
||||||
|
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'}<br>Recipient: ${recipient ? recipient[1] : 'Unknown'}`;
|
||||||
|
}
|
||||||
|
return event.content || 'No content available';
|
||||||
|
}
|
@@ -52,11 +52,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Update the filter to include additional kinds
|
// Update the filter to include additional kinds
|
||||||
const filter = {
|
const filter = {
|
||||||
kinds: [0, 1, 2, 3, 4, 6, 7, 10002, 30023], // Include multiple event kinds
|
kinds: [0, 1, 2, 3, 4, 6, 7, 10002, 30023, 10509], // Include all relevant kinds
|
||||||
authors: [pubkey],
|
authors: [pubkey], // Fetch events from the specified pubkey
|
||||||
limit: 1000,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
relayUrls.forEach((url) => {
|
relayUrls.forEach((url) => {
|
||||||
try {
|
try {
|
||||||
const sub = pool.sub([url], [filter]);
|
const sub = pool.sub([url], [filter]);
|
||||||
@@ -78,12 +77,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
case 4:
|
case 4:
|
||||||
console.log('Encrypted DM captured:', event);
|
console.log('Encrypted DM captured:', event);
|
||||||
break;
|
break;
|
||||||
|
case 10509:
|
||||||
|
console.log('Ephemeral DM captured:', event);
|
||||||
|
break;
|
||||||
case 30023:
|
case 30023:
|
||||||
console.log('Long-Form Content captured:', event);
|
console.log('Long-Form Content captured:', event);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Other event captured:', event);
|
console.log('Other event captured:', event);
|
||||||
}
|
}
|
||||||
|
|
||||||
collectedEvents.push(event); // Store the raw event data
|
collectedEvents.push(event); // Store the raw event data
|
||||||
eventIds.add(event.id);
|
eventIds.add(event.id);
|
||||||
}
|
}
|
||||||
|
102
src/styles.css
102
src/styles.css
@@ -1,50 +1,110 @@
|
|||||||
|
/* Base styles */
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
background-color: #f4f4f4;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 90%;
|
max-width: 1200px;
|
||||||
max-width: 600px;
|
margin: 20px auto;
|
||||||
margin: 50px auto;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Header and Stats */
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
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;
|
display: block;
|
||||||
margin-top: 15px;
|
margin: 20px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"],
|
/* Table styles */
|
||||||
textarea {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-top: 5px;
|
border: 1px solid #ddd;
|
||||||
box-sizing: border-box;
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
th {
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
background-color: #007BFF;
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background: #f4f4f4;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
th:hover {
|
||||||
background-color: #0056b3;
|
background: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#status {
|
.empty-message {
|
||||||
|
text-align: center;
|
||||||
|
color: #888;
|
||||||
margin-top: 20px;
|
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;
|
||||||
|
}
|
@@ -4,79 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Nostr Archive Viewer</title>
|
<title>Nostr Archive Viewer</title>
|
||||||
<style>
|
<link rel="stylesheet" href="styles.css">
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 20px auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.stats {
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
input[type="file"] {
|
|
||||||
display: block;
|
|
||||||
margin: 20px auto;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
th, td {
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
cursor: pointer;
|
|
||||||
background: #f4f4f4;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
th:hover {
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
.empty-message {
|
|
||||||
text-align: center;
|
|
||||||
color: #888;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.content-cell {
|
|
||||||
max-width: 600px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
.sort-indicator::after {
|
|
||||||
content: '⬍';
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
.sort-asc::after {
|
|
||||||
content: '↑';
|
|
||||||
}
|
|
||||||
.sort-desc::after {
|
|
||||||
content: '↓';
|
|
||||||
}
|
|
||||||
.number-column {
|
|
||||||
width: 50px;
|
|
||||||
text-align: right;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -98,198 +26,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="pagination">
|
||||||
|
<button id="prevPage" onclick="changePage(-1)" disabled>Previous</button>
|
||||||
|
<span id="pageInfo">Page 1</span>
|
||||||
|
<button id="nextPage" onclick="changePage(1)" disabled>Next</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="app.js"></script>
|
||||||
<script>
|
|
||||||
document.getElementById('fileInput').addEventListener('change', handleFileLoad);
|
|
||||||
|
|
||||||
let archiveData = [];
|
|
||||||
let currentSort = { column: 'created_at', order: 'desc' };
|
|
||||||
|
|
||||||
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;
|
|
||||||
updateStats();
|
|
||||||
sortTable(currentSort.column); // Initial sort
|
|
||||||
} catch (err) {
|
|
||||||
alert(`Failed to load archive: ${err.message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
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}<br>`;
|
|
||||||
statsText += 'Most Common Types: ';
|
|
||||||
|
|
||||||
// Get top 3 most common kinds
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTable() {
|
|
||||||
const tbody = document.querySelector('#dataTable tbody');
|
|
||||||
tbody.innerHTML = '';
|
|
||||||
|
|
||||||
if (archiveData.length === 0) {
|
|
||||||
tbody.innerHTML = `<tr class="empty-message">
|
|
||||||
<td colspan="4">No data loaded. Please load a JSON archive file.</td>
|
|
||||||
</tr>`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
archiveData.forEach((event, index) => {
|
|
||||||
const row = document.createElement('tr');
|
|
||||||
try {
|
|
||||||
row.innerHTML = `
|
|
||||||
<td class="number-column">${index + 1}</td>
|
|
||||||
<td>${new Date(event.created_at * 1000).toLocaleString()}</td>
|
|
||||||
<td>${getKindLabel(event.kind)}</td>
|
|
||||||
<td class="content-cell">${getContentDisplay(event)}</td>
|
|
||||||
`;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error rendering event ${event.id}:`, err);
|
|
||||||
row.innerHTML = `
|
|
||||||
<td class="number-column">${index + 1}</td>
|
|
||||||
<td>${new Date(event.created_at * 1000).toLocaleString()}</td>
|
|
||||||
<td>${getKindLabel(event.kind)}</td>
|
|
||||||
<td class="content-cell">Error loading content</td>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
tbody.appendChild(row);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update sort indicators
|
|
||||||
document.querySelectorAll('#dataTable th[data-sort] .sort-indicator').forEach(indicator => {
|
|
||||||
indicator.className = 'sort-indicator';
|
|
||||||
});
|
|
||||||
const currentHeader = document.querySelector(`th[data-sort="${currentSort.column}"] .sort-indicator`);
|
|
||||||
if (currentHeader) {
|
|
||||||
currentHeader.classList.add(`sort-${currentSort.order}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getKindLabel(kind) {
|
|
||||||
const kinds = {
|
|
||||||
0: 'Profile Metadata',
|
|
||||||
1: 'Short Text Note',
|
|
||||||
2: 'Recommend Relay',
|
|
||||||
3: 'Contacts',
|
|
||||||
4: 'Encrypted DM',
|
|
||||||
5: 'Event Deletion',
|
|
||||||
6: 'Repost',
|
|
||||||
7: 'Reaction',
|
|
||||||
8: 'Badge Award',
|
|
||||||
40: 'Channel Creation',
|
|
||||||
41: 'Channel Metadata',
|
|
||||||
42: 'Channel Message',
|
|
||||||
43: 'Channel Hide Message',
|
|
||||||
44: 'Channel Mute User',
|
|
||||||
1984: 'Reporting',
|
|
||||||
9734: 'Zap Request',
|
|
||||||
9735: 'Zap',
|
|
||||||
10000: 'Mute List',
|
|
||||||
10001: 'Pin List',
|
|
||||||
10002: 'Relay List Metadata',
|
|
||||||
30000: 'Follow List',
|
|
||||||
30001: 'Generic List',
|
|
||||||
30002: 'Relay List',
|
|
||||||
30003: 'Bookmark List',
|
|
||||||
30004: 'Communities',
|
|
||||||
30008: 'Profile Badges',
|
|
||||||
30009: 'Badge Definition',
|
|
||||||
30017: 'Create or update a stall',
|
|
||||||
30018: 'Create or update a product',
|
|
||||||
30023: 'Long-form Content',
|
|
||||||
30078: 'Application-specific Data',
|
|
||||||
30402: 'Classified Listing',
|
|
||||||
31989: 'Handler Recommendation',
|
|
||||||
31990: 'Handler Information'
|
|
||||||
};
|
|
||||||
return `${kind} (${kinds[kind] || 'Reserved/Custom'})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getContentDisplay(event) {
|
|
||||||
try {
|
|
||||||
if (event.kind === 0) {
|
|
||||||
const profile = JSON.parse(event.content || '{}');
|
|
||||||
return `Name: ${profile.name || 'N/A'}<br>
|
|
||||||
About: ${profile.about || 'N/A'}<br>
|
|
||||||
Picture: ${profile.picture || 'N/A'}`;
|
|
||||||
} else if (event.kind === 30023) {
|
|
||||||
// Display full long-form content
|
|
||||||
const content = event.content || '';
|
|
||||||
return content.replace(/\n/g, '<br>');
|
|
||||||
} else if (event.kind === 4) {
|
|
||||||
return `Encrypted Message: ${event.content || 'N/A'}`;
|
|
||||||
} else if (event.kind === 7) {
|
|
||||||
// Special handling for reactions
|
|
||||||
return `Reaction: ${event.content || '+'}`;
|
|
||||||
} else if (event.kind === 6) {
|
|
||||||
// Special handling for reposts
|
|
||||||
return `Repost: ${event.content || 'No content'}`;
|
|
||||||
} else if (event.kind === 3) {
|
|
||||||
// Special handling for contact lists
|
|
||||||
try {
|
|
||||||
const contacts = JSON.parse(event.content || '[]');
|
|
||||||
return `Contact List: ${contacts.length} contacts`;
|
|
||||||
} catch {
|
|
||||||
return `Contact List: Unable to parse`;
|
|
||||||
}
|
|
||||||
} else if (event.kind === 1) {
|
|
||||||
// Display full short text note
|
|
||||||
return event.content ? event.content.replace(/\n/g, '<br>') : 'No content available';
|
|
||||||
}
|
|
||||||
return event.content || 'No content available';
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error processing event ${event.id}:`, err);
|
|
||||||
return 'Error processing content';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortTable(column) {
|
|
||||||
if (currentSort.column === column) {
|
|
||||||
currentSort.order = currentSort.order === 'asc' ? 'desc' : 'asc';
|
|
||||||
} else {
|
|
||||||
currentSort.column = column;
|
|
||||||
currentSort.order = 'desc'; // Default to newest first
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = currentSort.order === 'asc' ? 1 : -1;
|
|
||||||
archiveData.sort((a, b) => {
|
|
||||||
if (a[column] < b[column]) return -1 * order;
|
|
||||||
if (a[column] > b[column]) return 1 * order;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
renderTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('#dataTable th[data-sort]').forEach(th => {
|
|
||||||
th.addEventListener('click', () => {
|
|
||||||
sortTable(th.dataset.sort);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Reference in New Issue
Block a user