feat: ticket watchers, fulltext search, single-query pagination, watcher notifications
Ticket watchers: - api/watch_ticket.php: GET (watch state) + POST (watch/unwatch toggle) - index.php: route for /api/watch_ticket.php - TicketView: WATCH/UNWATCH button with live state fetch and toggle - NotificationHelper::notifyWatchers(): fetches watchers from DB, resolves Matrix IDs via Synapse, fires notification to watchers + global list - add_comment.php, update_ticket.php: call notifyWatchers on comment and status-change events respectively Fulltext search: - TicketModel::hasFulltextIndex(): detects FULLTEXT index via information_schema - getAllTickets(): uses MATCH...AGAINST when fulltext index exists, LIKE fallback when not yet applied — zero-downtime rollout Single-query pagination: - getAllTickets() replaces separate COUNT + SELECT with COUNT(*) OVER() window function — one round trip to DB per page load instead of two Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,8 @@ include __DIR__ . '/layout_header.php';
|
||||
</option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
<button type="button" id="watchButton" class="lt-btn lt-btn-ghost lt-btn-sm"
|
||||
title="Watch this ticket to receive Matrix notifications on updates">WATCH</button>
|
||||
<button type="button" id="editButton" class="lt-btn lt-btn-primary lt-btn-sm">EDIT</button>
|
||||
<button type="button" id="cloneButton" class="lt-btn lt-btn-sm">CLONE</button>
|
||||
<a id="exportFullBtn"
|
||||
@@ -768,6 +770,49 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
}
|
||||
|
||||
// Watch / Unwatch button
|
||||
var watchBtn = document.getElementById('watchButton');
|
||||
if (watchBtn) {
|
||||
var _watching = false;
|
||||
// Fetch initial state
|
||||
lt.api.get('/api/watch_ticket.php?ticket_id=' + window.ticketData.ticket_id)
|
||||
.then(function (d) {
|
||||
if (d.success) {
|
||||
_watching = d.watching;
|
||||
watchBtn.textContent = _watching ? 'UNWATCH' : 'WATCH';
|
||||
watchBtn.title = _watching
|
||||
? 'You are watching this ticket. Click to stop.'
|
||||
: 'Watch this ticket for Matrix notifications on updates.';
|
||||
if (_watching) watchBtn.classList.add('lt-btn-active');
|
||||
}
|
||||
})
|
||||
.catch(function () {});
|
||||
|
||||
watchBtn.addEventListener('click', function () {
|
||||
var action = _watching ? 'unwatch' : 'watch';
|
||||
watchBtn.disabled = true;
|
||||
lt.api.post('/api/watch_ticket.php', { ticket_id: window.ticketData.ticket_id, action: action })
|
||||
.then(function (d) {
|
||||
if (d.success) {
|
||||
_watching = d.watching;
|
||||
watchBtn.textContent = _watching ? 'UNWATCH' : 'WATCH';
|
||||
watchBtn.title = _watching
|
||||
? 'You are watching this ticket. Click to stop.'
|
||||
: 'Watch this ticket for Matrix notifications on updates.';
|
||||
watchBtn.classList.toggle('lt-btn-active', _watching);
|
||||
lt.toast.success(_watching ? 'Watching ticket' : 'Stopped watching ticket');
|
||||
} else {
|
||||
lt.toast.error('Failed: ' + (d.error || 'Unknown error'));
|
||||
}
|
||||
watchBtn.disabled = false;
|
||||
})
|
||||
.catch(function () {
|
||||
lt.toast.error('Failed to update watch status');
|
||||
watchBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add comment button
|
||||
var addCommentBtn = document.getElementById('addCommentBtn');
|
||||
if (addCommentBtn) {
|
||||
|
||||
Reference in New Issue
Block a user