diff --git a/README.md b/README.md index baff03f..81bad77 100644 --- a/README.md +++ b/README.md @@ -259,8 +259,8 @@ tinker_tickets/ │ │ ├── markdown.js # Markdown rendering + ticket linking (XSS-safe) │ │ ├── settings.js # User preferences │ │ ├── ticket.js # Ticket + comments + visibility -│ │ ├── toast.js # Backwards-compat shim → delegates to lt.toast -│ │ └── utils.js # escapeHtml (→ lt.escHtml) + getTicketIdFromUrl +│ │ ├── toast.js # Deprecated shim (no longer loaded — all callers use lt.toast directly) +│ │ └── utils.js # escapeHtml (→ lt.escHtml), getTicketIdFromUrl, showConfirmModal │ └── images/ │ └── favicon.png ├── config/ @@ -397,6 +397,12 @@ Key conventions and gotchas for working with this codebase: 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 +20. **No raw `fetch()`**: All AJAX calls use `lt.api.get/post/put/delete()` — never use raw `fetch()`. The wrapper auto-adds `Content-Type: application/json` and `X-CSRF-Token`, auto-parses JSON, and throws on non-2xx. +21. **Confirm dialogs**: Never use browser `confirm()`. Use `showConfirmModal(title, message, type, onConfirm)` (defined in `utils.js`, available on all pages). Types: `'warning'` | `'error'` | `'info'`. +22. **`utils.js` on all pages**: `utils.js` is loaded by all views (including admin). It provides `escapeHtml()`, `getTicketIdFromUrl()`, and `showConfirmModal()`. +23. **No `toast.js`**: `toast.js` is deprecated and no longer loaded by any view. Use `lt.toast.success/error/warning/info()` directly from `base.js`. +24. **CSS utility classes** (dashboard.css): Use `.text-green`, `.text-amber`, `.text-cyan`, `.text-danger`, `.text-open`, `.text-closed`, `.text-muted`, `.text-muted-green`, `.text-sm`, `.text-center`, `.nowrap`, `.mono`, `.fw-bold`, `.mb-1` for typography. Use `.admin-container` (1200px) or `.admin-container-wide` (1400px) for admin page max-widths. Use `.admin-header-row` for title+button header rows. Use `.admin-form-row`, `.admin-form-field`, `.admin-label`, `.admin-input` for filter forms. Use `.admin-form-actions` for filter form button groups. Use `.table-wrapper` + `td.empty-state` for tables. Use `.setting-grid-2` and `.setting-grid-3` for modal form grids. Use `.lt-modal-sm` or `.lt-modal-lg` for modal sizing. +25. **CSS utility classes** (ticket.css): Use `.form-hint` (green helper text), `.form-hint-warning` (amber helper text), `.visibility-groups-list` (checkbox row), `.group-checkbox-label` (checkbox label) for ticket forms. ## File Reference diff --git a/assets/css/base.css b/assets/css/base.css index e2b3739..158488b 100644 --- a/assets/css/base.css +++ b/assets/css/base.css @@ -922,7 +922,8 @@ pre { .lt-table-wrap { overflow-x: auto; border: 2px solid var(--terminal-green); } /* Sortable column header */ -.lt-table th[data-sort] { cursor: pointer; } +.lt-table th[data-sort], +th[data-sort-key] { cursor: pointer; } .lt-table th[data-sort]:hover { color: var(--terminal-green); text-shadow: var(--glow-green); } .lt-table th[data-sort="asc"]::after { content: ' ▲'; color: var(--terminal-green); } .lt-table th[data-sort="desc"]::after { content: ' ▼'; color: var(--terminal-green); } @@ -1177,6 +1178,7 @@ pre { .lt-toast-close::before, .lt-toast-close::after { content: ''; } .lt-toast-close:hover { opacity: 1; transform: none; } +.lt-toast--hiding { opacity: 0; transition: opacity 0.3s ease; } /* ---------------------------------------------------------------- 15. TAB NAVIGATION diff --git a/assets/css/dashboard.css b/assets/css/dashboard.css index b9981bd..852e9ce 100644 --- a/assets/css/dashboard.css +++ b/assets/css/dashboard.css @@ -284,6 +284,8 @@ tbody tr { transition: background-color 0.15s ease; } +tr[data-clickable="true"] { cursor: pointer; } + /* Button press effect */ .btn { transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; @@ -1405,8 +1407,12 @@ h1 { z-index: var(--z-dropdown); font-family: var(--font-mono); color: var(--terminal-green); + transition: opacity 0.3s; } +.loading-overlay--hiding { opacity: 0; } +.has-overlay { position: relative; } + .loading-overlay .loading-text { margin-top: 1rem; animation: blink-cursor 1s step-end infinite; @@ -1504,6 +1510,27 @@ h1 { opacity: 0.7; } +/* Inside table cells: compact version — no ASCII art, reduced padding */ +td.empty-state { + padding: 2rem; + color: var(--terminal-green-dim); +} + +td.empty-state::before { + display: none; +} + +/* Admin views max-width container */ +.admin-container { + max-width: 1200px; + margin: 2rem auto; +} + +.admin-container-wide { + max-width: 1400px; + margin: 2rem auto; +} + /* ===== LAYOUT COMPONENTS ===== */ .dashboard-header { display: flex; @@ -1580,11 +1607,6 @@ button { transform: translateY(0); } -@keyframes pulse-glow-box { - 0%, 100% { box-shadow: 0 0 8px currentColor, 0 0 16px currentColor; } - 50% { box-shadow: 0 0 12px currentColor, 0 0 24px currentColor, 0 0 32px currentColor; } -} - /* Terminal prompt for primary action buttons */ .btn.create-ticket::before, .btn.primary::before { @@ -2217,6 +2239,19 @@ input[type="checkbox"]:checked { } /* ===== COLLAPSIBLE ASCII BANNER ===== */ +.ascii-banner { + margin: 0; + font-family: var(--font-mono); + color: var(--terminal-green); + line-height: 1.2; + white-space: pre; + overflow: visible; + text-align: center; +} + +.ascii-banner--glow { text-shadow: var(--glow-green); } +.ascii-banner-cursor { margin-left: 5px; animation: blink-cursor 0.75s step-end infinite; } + .ascii-banner-wrapper { max-width: 100%; margin: 0 1rem 1rem 1rem; @@ -2774,6 +2809,8 @@ input[type="checkbox"]:checked { display: flex; gap: 0.5rem; align-items: center; + justify-content: center; + margin-top: 1rem; } .pagination button { @@ -3905,6 +3942,279 @@ body.modal-open { height: 100%; } +/* ======================================== + ADMIN FORM UTILITIES + Shared across all admin views (WorkflowDesigner, CustomFields, + Templates, RecurringTickets, ApiKeys). + ======================================== */ + +.admin-section-title { + color: var(--terminal-amber); + margin-bottom: 1rem; +} + +/* Horizontal flex row for form fields */ +.admin-form-row { + display: flex; + gap: 1rem; + flex-wrap: wrap; + align-items: flex-end; + margin-bottom: 1rem; +} + +/* Growable field within .admin-form-row */ +.admin-form-field { + flex: 1; + min-width: 150px; +} + +/* Compact label above admin inputs */ +.admin-label { + display: block; + font-size: 0.8rem; + color: var(--terminal-amber); + margin-bottom: 0.25rem; +} + +/* Styled input / select / textarea for admin forms */ +.admin-input { + width: 100%; + padding: 0.5rem; + border: 2px solid var(--terminal-green); + border-radius: 0; + background: var(--bg-primary); + color: var(--terminal-green); + font-family: var(--font-mono); +} + +.admin-input:focus { + outline: none; + border-color: var(--terminal-amber); +} + +/* Admin page title in user-header (breadcrumb-style label) */ +.admin-page-title { + margin-left: 1rem; + color: var(--terminal-amber); +} + +/* Utility: prevent text wrap (e.g., date columns in tables) */ +.nowrap { white-space: nowrap; } + +/* Utility: monospace font */ +.mono { font-family: var(--font-mono); } + +/* Utility: color helpers for status / accent text */ +.text-green { color: var(--terminal-green); } +.text-amber { color: var(--terminal-amber); } +.text-cyan { color: var(--terminal-cyan); } +.text-danger { color: var(--priority-1); } +.text-open { color: var(--status-open); } +.text-closed { color: var(--status-closed); } +.text-muted-green { color: var(--terminal-green-dim); } + +/* Pre / code display block in admin UI */ +.admin-code-block { + background: var(--bg-primary); + padding: 1rem; + border: 1px solid var(--terminal-green); + overflow-x: auto; + font-family: var(--font-mono); +} + +/* Summary stats grid (4 columns, used in UserActivityView) */ +.admin-stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1rem; + text-align: center; + margin-top: 2rem; + padding: 1rem; + border: 1px solid var(--terminal-green); +} + +.admin-stat-value { + font-size: 1.5rem; + font-weight: bold; +} + +.admin-stat-label { + font-size: 0.8rem; + color: var(--terminal-green-dim); +} + +/* Row with title on left and action button on right */ +.admin-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.admin-header-row h2 { + margin: 0; +} + +/* Font weight utility */ +.fw-bold { font-weight: bold; } + +/* Small button variant (shared with ticket.css) */ +.btn-small { + padding: 0.4rem 0.75rem !important; + font-size: 0.85rem !important; + min-height: auto !important; +} + +/* Boot banner centering */ +#boot-banner { + text-align: center; + margin-bottom: 1rem; +} + +/* Empty state for dashboard table (PHP-generated) */ +.dashboard-empty-state { + text-align: center; + padding: 3rem; +} + +/* Fixed-width table columns */ +.col-checkbox { width: 40px; } +.col-actions { width: 100px; } + +.dashboard-empty-pre { + color: var(--terminal-green); + text-shadow: var(--glow-green); + font-size: 0.8rem; + line-height: 1.2; +} + +/* Grid layouts for modal form bodies */ +.setting-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; } +.setting-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } + +/* Workflow status diagram container */ +.workflow-diagram { + margin-bottom: 2rem; + padding: 1rem; + border: 1px solid var(--terminal-green); + background: var(--bg-secondary); +} + +.workflow-diagram-nodes { + display: flex; + justify-content: center; + gap: 2rem; + flex-wrap: wrap; +} + +.workflow-diagram-node { + text-align: center; +} + +.workflow-diagram-node-label { + font-size: 0.8rem; + margin-top: 0.5rem; +} + +/* Inline paragraph spacing utility */ +.mb-1 { margin-bottom: 1rem; } + +/* Smaller modal size variants (complement lt-modal-lg from base.css) */ +.lt-modal-sm { width: 460px; } + +/* Half-rem margin-bottom utility */ +.mb-half { margin-bottom: 0.5rem; } + +/* Modal form label (block display with bottom spacing) */ +.lt-modal-body label { + display: block; + margin-bottom: 0.5rem; + color: var(--terminal-green); +} + +/* Modal form select/input top spacing */ +.lt-modal-body .lt-select, +.lt-modal-body .lt-input { + margin-top: 0.5rem; +} + +/* Extra-small modal size (400px) */ +.lt-modal-xs { width: 400px; } + +/* Confirmation modal body message */ +.modal-message { + color: var(--terminal-green); + white-space: pre-line; +} + +/* Danger modal header variant */ +.lt-modal-header--danger { + color: var(--status-closed) !important; +} + +/* Danger action button */ +.lt-btn-danger { + background: var(--status-closed); + border-color: var(--status-closed); + color: var(--bg-primary); +} + +.lt-btn-danger:hover { + background: var(--priority-1); + border-color: var(--priority-1); +} + +/* Modal body text sizing */ +.modal-warning-text { + color: var(--terminal-amber); + font-size: 1.1rem; + margin-bottom: 1rem; +} + +/* Keyboard shortcuts modal */ +.kb-section-heading { + color: var(--terminal-amber); + margin: 0 0 0.5rem 0; + font-size: 0.9rem; +} + +.kb-shortcuts-table { + width: 100%; + border-collapse: collapse; + margin-bottom: 1rem; +} + +.kb-shortcuts-table.no-margin { + margin-bottom: 0; +} + +.kb-shortcuts-table td { + padding: 0.4rem; + border-bottom: 1px solid var(--border-color); +} + +.kb-shortcuts-table tr:last-child td { + border-bottom: none; +} + +/* Button/action group aligned to bottom (used in filter forms) */ +.admin-form-actions { + display: flex; + align-items: flex-end; + gap: 0.5rem; +} + +/* Truncated table cell for long content */ +.td-truncate { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Small text utility */ +.text-sm { font-size: 0.85rem; } + /* ======================================== MARKDOWN FORMATTING STYLES ======================================== */ diff --git a/assets/css/ticket.css b/assets/css/ticket.css index 84179ed..a852321 100644 --- a/assets/css/ticket.css +++ b/assets/css/ticket.css @@ -466,6 +466,50 @@ textarea[data-field="description"]:not(:disabled)::after { } /* Form Elements */ +/* Helper text below form fields */ +.form-hint { + color: var(--terminal-green); + font-family: var(--font-mono); + font-size: 0.85rem; + margin-top: 0.5rem; +} + +.form-hint-warning { + color: var(--terminal-amber); + font-family: var(--font-mono); + font-size: 0.85rem; + margin-top: 0.5rem; +} + +/* Visibility group checkbox row */ +.visibility-groups-list { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 0.5rem; +} + +.group-checkbox-label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; +} + +/* Duplicate warning box and visibility groups (JS-toggled, need margin when visible) */ +#duplicateWarning { + margin-top: 1rem; +} + +#visibilityGroupsContainer { + margin-top: 1rem; +} + +/* Duplicate found heading */ +.duplicate-heading { + margin-bottom: 0.5rem; +} + .detail-group { margin-bottom: 30px; padding: 15px; @@ -944,6 +988,10 @@ textarea.editable { } } +.animate-fadein { animation: fadeIn 0.3s ease; } +.animate-fadeout { animation: fadeIn 0.2s ease reverse; } +.comment--deleting { opacity: 0; transform: translateX(-20px); transition: opacity 0.3s, transform 0.3s; } + .reply-form-container .reply-header { display: flex; justify-content: space-between; diff --git a/assets/js/advanced-search.js b/assets/js/advanced-search.js index 4cb1ac8..e64616e 100644 --- a/assets/js/advanced-search.js +++ b/assets/js/advanced-search.js @@ -19,14 +19,6 @@ function closeAdvancedSearch() { lt.modal.close('advancedSearchModal'); } -// Close modal when clicking on backdrop -function closeOnAdvancedSearchBackdropClick(event) { - const modal = document.getElementById('advancedSearchModal'); - if (event.target === modal) { - closeAdvancedSearch(); - } -} - // Load users for dropdown async function loadUsersForSearch() { try { diff --git a/assets/js/ascii-banner.js b/assets/js/ascii-banner.js index 2144f23..2e17305 100644 --- a/assets/js/ascii-banner.js +++ b/assets/js/ascii-banner.js @@ -65,20 +65,8 @@ function renderASCIIBanner(bannerId, containerSelector, speed = 5, addGlow = tru // Create pre element for ASCII art const pre = document.createElement('pre'); - pre.className = 'ascii-banner'; - pre.style.margin = '0'; - pre.style.fontFamily = 'var(--font-mono)'; - pre.style.color = 'var(--terminal-green)'; - - if (addGlow) { - pre.style.textShadow = 'var(--glow-green)'; - } - + pre.className = addGlow ? 'ascii-banner ascii-banner--glow' : 'ascii-banner'; pre.style.fontSize = getBannerFontSize(bannerId); - pre.style.lineHeight = '1.2'; - pre.style.whiteSpace = 'pre'; - pre.style.overflow = 'visible'; - pre.style.textAlign = 'center'; container.appendChild(pre); @@ -178,8 +166,7 @@ function animatedWelcome(containerSelector) { banner.addEventListener('bannerComplete', () => { const cursor = document.createElement('span'); cursor.textContent = '█'; - cursor.style.animation = 'blink-caret 0.75s step-end infinite'; - cursor.style.marginLeft = '5px'; + cursor.className = 'ascii-banner-cursor'; banner.appendChild(cursor); }); } diff --git a/assets/js/base.js b/assets/js/base.js index 6caf677..ebb61fa 100644 --- a/assets/js/base.js +++ b/assets/js/base.js @@ -124,8 +124,7 @@ function _dismissToast(toast) { if (!toast || !toast.parentNode) return; clearTimeout(toast._lt_timer); - toast.style.opacity = '0'; - toast.style.transition = 'opacity 0.3s ease'; + toast.classList.add('lt-toast--hiding'); setTimeout(() => { if (toast.parentNode) toast.parentNode.removeChild(toast); _toastActive = false; @@ -176,11 +175,11 @@ lt.modal.closeAll(); HTML contract: -