Integrate lt.time.ago() for dashboard timestamps, update README
- Add data-ts attributes to table and card view date cells so JS can
convert them to relative time ("2h ago") while keeping the full date
in the title attribute for hover tooltips
- Add initRelativeTimes() in dashboard.js using lt.time.ago(); runs on
DOMContentLoaded and refreshes every 60s so times stay current
- Fix table sort for date columns to read data-ts attribute instead of
text content (which is now relative and not sortable as a date)
- Update README: add base.css/base.js/utils.js to project structure,
fix ascii-banner.js description, expand keyboard shortcuts table,
add developer notes for lt.time and boot sequence behavior
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
16
README.md
16
README.md
@@ -126,6 +126,11 @@ Access all admin pages via the **Admin dropdown** in the dashboard header.
|
||||
| `Ctrl/Cmd + E` | Toggle edit mode (ticket page) |
|
||||
| `Ctrl/Cmd + S` | Save changes (ticket page) |
|
||||
| `Ctrl/Cmd + K` | Focus search box (dashboard) |
|
||||
| `N` | New ticket (dashboard) |
|
||||
| `J` / `K` | Next / previous row (dashboard table) |
|
||||
| `Enter` | Open selected ticket (dashboard) |
|
||||
| `G` then `D` | Go to dashboard |
|
||||
| `1`–`4` | Quick status change (ticket page) |
|
||||
| `ESC` | Cancel edit / close modal |
|
||||
| `?` | Show keyboard shortcuts help |
|
||||
|
||||
@@ -242,17 +247,20 @@ tinker_tickets/
|
||||
│ └── upload_attachment.php # GET/POST: List or upload attachments
|
||||
├── assets/
|
||||
│ ├── css/
|
||||
│ │ ├── base.css # LotusGuild Terminal Design System (symlinked from web_template)
|
||||
│ │ ├── dashboard.css # Dashboard + terminal styling
|
||||
│ │ └── ticket.css # Ticket view styling
|
||||
│ ├── js/
|
||||
│ │ ├── advanced-search.js # Advanced search modal
|
||||
│ │ ├── ascii-banner.js # ASCII art banner
|
||||
│ │ ├── ascii-banner.js # ASCII art banner (rendered in boot overlay on first visit)
|
||||
│ │ ├── base.js # LotusGuild JS utilities — window.lt (symlinked from web_template)
|
||||
│ │ ├── dashboard.js # Dashboard + bulk actions + kanban + sidebar
|
||||
│ │ ├── keyboard-shortcuts.js # Keyboard shortcuts
|
||||
│ │ ├── keyboard-shortcuts.js # Keyboard shortcuts (uses lt.keys)
|
||||
│ │ ├── markdown.js # Markdown rendering + ticket linking (XSS-safe)
|
||||
│ │ ├── settings.js # User preferences
|
||||
│ │ ├── ticket.js # Ticket + comments + visibility
|
||||
│ │ └── toast.js # Toast notifications
|
||||
│ │ ├── toast.js # Backwards-compat shim → delegates to lt.toast
|
||||
│ │ └── utils.js # escapeHtml (→ lt.escHtml) + getTicketIdFromUrl
|
||||
│ └── images/
|
||||
│ └── favicon.png
|
||||
├── config/
|
||||
@@ -387,6 +395,8 @@ Key conventions and gotchas for working with this codebase:
|
||||
15. **CSP nonces**: All inline `<script>` tags require `nonce="<?php echo $nonce; ?>"`
|
||||
16. **Visibility validation**: Internal visibility requires at least one group — validated server-side
|
||||
17. **Rate limiting**: Both session-based AND IP-based limits are enforced
|
||||
18. **Relative timestamps**: Dashboard dates use `lt.time.ago()` and refresh every 60s; full date is always in the `title` attribute for hover
|
||||
19. **Boot sequence**: Shows ASCII banner then boot messages on first page visit per session (`sessionStorage.getItem('booted')`); removed on subsequent loads
|
||||
|
||||
## File Reference
|
||||
|
||||
|
||||
@@ -366,11 +366,13 @@ function sortTable(table, column) {
|
||||
const aValue = a.children[column].textContent.trim();
|
||||
const bValue = b.children[column].textContent.trim();
|
||||
|
||||
// Check if this is a date column
|
||||
// Check if this is a date column — prefer data-ts attribute over text (which may be relative)
|
||||
const headerText = headers[column].textContent.toLowerCase();
|
||||
if (headerText === 'created' || headerText === 'updated') {
|
||||
const dateA = new Date(aValue);
|
||||
const dateB = new Date(bValue);
|
||||
const cellA = a.children[column];
|
||||
const cellB = b.children[column];
|
||||
const dateA = new Date(cellA.dataset.ts || aValue);
|
||||
const dateB = new Date(cellB.dataset.ts || bValue);
|
||||
return currentDirection === 'asc' ? dateA - dateB : dateB - dateA;
|
||||
}
|
||||
|
||||
@@ -1817,6 +1819,24 @@ function hideLoadingOverlay(element) {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// RELATIVE TIMESTAMPS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Convert all .ts-cell[data-ts] elements to relative time using lt.time.ago().
|
||||
* Runs once on DOMContentLoaded and refreshes every 60s so "2m ago" stays current.
|
||||
* The original full timestamp is preserved in the title attribute for hover.
|
||||
*/
|
||||
function initRelativeTimes() {
|
||||
document.querySelectorAll('.ts-cell[data-ts]').forEach(el => {
|
||||
el.textContent = lt.time.ago(el.dataset.ts);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initRelativeTimes);
|
||||
setInterval(initRelativeTimes, 60000);
|
||||
|
||||
// Export for use in other scripts
|
||||
window.generateSkeletonRows = generateSkeletonRows;
|
||||
window.generateSkeletonComments = generateSkeletonComments;
|
||||
|
||||
@@ -445,8 +445,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
||||
echo "<td><span class='status-" . str_replace(' ', '-', $row['status']) . "'>{$row['status']}</span></td>";
|
||||
echo "<td>" . htmlspecialchars($creator) . "</td>";
|
||||
echo "<td>" . htmlspecialchars($assignedTo) . "</td>";
|
||||
echo "<td>" . date('Y-m-d H:i', strtotime($row['created_at'])) . "</td>";
|
||||
echo "<td>" . date('Y-m-d H:i', strtotime($row['updated_at'])) . "</td>";
|
||||
echo "<td class='ts-cell' data-ts='" . htmlspecialchars($row['created_at'], ENT_QUOTES, 'UTF-8') . "' title='" . date('Y-m-d H:i T', strtotime($row['created_at'])) . "'>" . date('Y-m-d H:i', strtotime($row['created_at'])) . "</td>";
|
||||
echo "<td class='ts-cell' data-ts='" . htmlspecialchars($row['updated_at'], ENT_QUOTES, 'UTF-8') . "' title='" . date('Y-m-d H:i T', strtotime($row['updated_at'])) . "'>" . date('Y-m-d H:i', strtotime($row['updated_at'])) . "</td>";
|
||||
// Quick actions column
|
||||
echo "<td class='quick-actions-cell'>";
|
||||
echo "<div class='quick-actions'>";
|
||||
@@ -493,7 +493,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
||||
<div class="ticket-card-meta">
|
||||
<span><?php echo htmlspecialchars($row['category']); ?></span>
|
||||
<span>@ <?php echo htmlspecialchars($assignedTo); ?></span>
|
||||
<span><?php echo date('M j', strtotime($row['updated_at'])); ?></span>
|
||||
<span class="ts-cell" data-ts="<?php echo htmlspecialchars($row['updated_at'], ENT_QUOTES, 'UTF-8'); ?>" title="<?php echo date('Y-m-d H:i', strtotime($row['updated_at'])); ?>"><?php echo date('M j', strtotime($row['updated_at'])); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ticket-card-status <?php echo $statusClass; ?>">
|
||||
|
||||
Reference in New Issue
Block a user