Use AJAX for notifications table (#10961)
* Use AJAX for notifications table Signed-off-by: Andrew Thornton <art27@cantab.net> * move to separate js Signed-off-by: Andrew Thornton <art27@cantab.net> * placate golangci-lint Signed-off-by: Andrew Thornton <art27@cantab.net> * Add autoupdating notification count Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix wipeall Signed-off-by: Andrew Thornton <art27@cantab.net> * placate tests Signed-off-by: Andrew Thornton <art27@cantab.net> * Try hidden Signed-off-by: Andrew Thornton <art27@cantab.net> * Try hide and hidden Signed-off-by: Andrew Thornton <art27@cantab.net> * More auto-update improvements Only run checker on pages that have a count Change starting checker to 10s with a back-off to 60s if there is no change Signed-off-by: Andrew Thornton <art27@cantab.net> * string comparison! Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @silverwind Signed-off-by: Andrew Thornton <art27@cantab.net> * add configurability as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * Add documentation as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> * Use CSRF header not query Signed-off-by: Andrew Thornton <art27@cantab.net> * Further JS improvements Fix @etzelia update notification table request Fix @silverwind comments Co-Authored-By: silverwind <me@silverwind.io> Signed-off-by: Andrew Thornton <art27@cantab.net> * Simplify the notification count fns Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: silverwind <me@silverwind.io>tokarchuk/v1.17
parent
e74c4e1be9
commit
b10c416f9e
@ -1,119 +1,3 @@ |
|||||||
{{template "base/head" .}} |
{{template "base/head" .}} |
||||||
|
{{template "user/notification/notification_div" .}} |
||||||
<div class="user notification"> |
|
||||||
<div class="ui container"> |
|
||||||
<h1 class="ui dividing header">{{.i18n.Tr "notification.notifications"}}</h1> |
|
||||||
|
|
||||||
<div class="ui top attached tabular menu"> |
|
||||||
<a href="{{AppSubUrl}}/notifications?q=unread" class="{{if eq .Status 1}}active{{end}} item"> |
|
||||||
{{.i18n.Tr "notification.unread"}} |
|
||||||
{{if .NotificationUnreadCount}} |
|
||||||
<div class="ui label">{{.NotificationUnreadCount}}</div> |
|
||||||
{{end}} |
|
||||||
</a> |
|
||||||
<a href="{{AppSubUrl}}/notifications?q=read" class="{{if eq .Status 2}}active{{end}} item"> |
|
||||||
{{.i18n.Tr "notification.read"}} |
|
||||||
</a> |
|
||||||
{{if and (eq .Status 1) (.NotificationUnreadCount)}} |
|
||||||
<form action="{{AppSubUrl}}/notifications/purge" method="POST" style="margin-left: auto;"> |
|
||||||
{{$.CsrfTokenHtml}} |
|
||||||
<button class="ui mini button primary" title='{{$.i18n.Tr "notification.mark_all_as_read"}}'> |
|
||||||
{{svg "octicon-checklist" 16}} |
|
||||||
</button> |
|
||||||
</form> |
|
||||||
{{end}} |
|
||||||
</div> |
|
||||||
<div class="ui bottom attached active tab segment"> |
|
||||||
{{if eq (len .Notifications) 0}} |
|
||||||
{{if eq .Status 1}} |
|
||||||
{{.i18n.Tr "notification.no_unread"}} |
|
||||||
{{else}} |
|
||||||
{{.i18n.Tr "notification.no_read"}} |
|
||||||
{{end}} |
|
||||||
{{else}} |
|
||||||
<table class="ui unstackable striped very compact small selectable table"> |
|
||||||
<tbody> |
|
||||||
{{range $notification := .Notifications}} |
|
||||||
{{$issue := $notification.Issue}} |
|
||||||
{{$repo := $notification.Repository}} |
|
||||||
{{$repoOwner := $repo.MustOwner}} |
|
||||||
|
|
||||||
<tr data-href="{{$notification.HTMLURL}}"> |
|
||||||
<td class="collapsing"> |
|
||||||
{{if eq $notification.Status 3}} |
|
||||||
<span class="blue">{{svg "octicon-pin" 16}}</span> |
|
||||||
{{else if $issue.IsPull}} |
|
||||||
{{if $issue.IsClosed}} |
|
||||||
{{if $issue.GetPullRequest.HasMerged}} |
|
||||||
<span class="purple">{{svg "octicon-git-merge" 16}}</span> |
|
||||||
{{else}} |
|
||||||
<span class="red">{{svg "octicon-git-pull-request" 16}}</span> |
|
||||||
{{end}} |
|
||||||
{{else}} |
|
||||||
<span class="green">{{svg "octicon-git-pull-request" 16}}</span> |
|
||||||
{{end}} |
|
||||||
{{else}} |
|
||||||
{{if $issue.IsClosed}} |
|
||||||
<span class="red">{{svg "octicon-issue-closed" 16}}</span> |
|
||||||
{{else}} |
|
||||||
<span class="green">{{svg "octicon-issue-opened" 16}}</span> |
|
||||||
{{end}} |
|
||||||
{{end}} |
|
||||||
</td> |
|
||||||
<td class="eleven wide"> |
|
||||||
<a class="item" href="{{$notification.HTMLURL}}"> |
|
||||||
#{{$issue.Index}} - {{$issue.Title}} |
|
||||||
</a> |
|
||||||
</td> |
|
||||||
<td> |
|
||||||
<a class="item" href="{{AppSubUrl}}/{{$repoOwner.Name}}/{{$repo.Name}}"> |
|
||||||
{{$repoOwner.Name}}/{{$repo.Name}} |
|
||||||
</a> |
|
||||||
</td> |
|
||||||
<td class="collapsing"> |
|
||||||
{{if ne $notification.Status 3}} |
|
||||||
<form action="{{AppSubUrl}}/notifications/status" method="POST"> |
|
||||||
{{$.CsrfTokenHtml}} |
|
||||||
<input type="hidden" name="notification_id" value="{{$notification.ID}}" /> |
|
||||||
<input type="hidden" name="status" value="pinned" /> |
|
||||||
<button class="ui mini button" title='{{$.i18n.Tr "notification.pin"}}'> |
|
||||||
{{svg "octicon-pin" 16}} |
|
||||||
</button> |
|
||||||
</form> |
|
||||||
{{end}} |
|
||||||
</td> |
|
||||||
<td class="collapsing"> |
|
||||||
{{if or (eq $notification.Status 1) (eq $notification.Status 3)}} |
|
||||||
<form action="{{AppSubUrl}}/notifications/status" method="POST"> |
|
||||||
{{$.CsrfTokenHtml}} |
|
||||||
<input type="hidden" name="notification_id" value="{{$notification.ID}}" /> |
|
||||||
<input type="hidden" name="status" value="read" /> |
|
||||||
<input type="hidden" name="page" value="{{$.Page.Paginater.Current}}" /> |
|
||||||
<button class="ui mini button" title='{{$.i18n.Tr "notification.mark_as_read"}}'> |
|
||||||
{{svg "octicon-check" 16}} |
|
||||||
</button> |
|
||||||
</form> |
|
||||||
{{else if eq $notification.Status 2}} |
|
||||||
<form action="{{AppSubUrl}}/notifications/status" method="POST"> |
|
||||||
{{$.CsrfTokenHtml}} |
|
||||||
<input type="hidden" name="notification_id" value="{{$notification.ID}}" /> |
|
||||||
<input type="hidden" name="status" value="unread" /> |
|
||||||
<input type="hidden" name="page" value="{{$.Page.Paginater.Current}}" /> |
|
||||||
<button class="ui mini button" title='{{$.i18n.Tr "notification.mark_as_unread"}}'> |
|
||||||
{{svg "octicon-bell" 16}} |
|
||||||
</button> |
|
||||||
</form> |
|
||||||
{{end}} |
|
||||||
</td> |
|
||||||
</tr> |
|
||||||
{{end}} |
|
||||||
</tbody> |
|
||||||
</table> |
|
||||||
{{end}} |
|
||||||
</div> |
|
||||||
|
|
||||||
{{template "base/paginate" .}} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
{{template "base/footer" .}} |
{{template "base/footer" .}} |
||||||
|
@ -0,0 +1,128 @@ |
|||||||
|
<div class="user notification" id="notification_div" data-params="{{.Page.GetParams}}"> |
||||||
|
<div class="ui container"> |
||||||
|
<h1 class="ui dividing header">{{.i18n.Tr "notification.notifications"}}</h1> |
||||||
|
<div class="ui top attached tabular menu"> |
||||||
|
{{ $notificationUnreadCount := call .NotificationUnreadCount}} |
||||||
|
<a href="{{AppSubUrl}}/notifications?q=unread" class="{{if eq .Status 1}}active{{end}} item"> |
||||||
|
{{.i18n.Tr "notification.unread"}} |
||||||
|
<div class="ui label {{if not $notificationUnreadCount}}hidden{{end}}">{{$notificationUnreadCount}}</div> |
||||||
|
</a> |
||||||
|
<a href="{{AppSubUrl}}/notifications?q=read" class="{{if eq .Status 2}}active{{end}} item"> |
||||||
|
{{.i18n.Tr "notification.read"}} |
||||||
|
</a> |
||||||
|
{{if and (eq .Status 1)}} |
||||||
|
<form action="{{AppSubUrl}}/notifications/purge" method="POST" style="margin-left: auto;"> |
||||||
|
{{$.CsrfTokenHtml}} |
||||||
|
<div class="{{if not $notificationUnreadCount}}hide{{end}}"> |
||||||
|
<button class="ui mini button primary" title='{{$.i18n.Tr "notification.mark_all_as_read"}}'> |
||||||
|
{{svg "octicon-checklist" 16}} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
{{end}} |
||||||
|
</div> |
||||||
|
<div class="ui bottom attached active tab segment"> |
||||||
|
{{if eq (len .Notifications) 0}} |
||||||
|
{{if eq .Status 1}} |
||||||
|
{{.i18n.Tr "notification.no_unread"}} |
||||||
|
{{else}} |
||||||
|
{{.i18n.Tr "notification.no_read"}} |
||||||
|
{{end}} |
||||||
|
{{else}} |
||||||
|
<table class="ui unstackable striped very compact small selectable table" id="notification_table"> |
||||||
|
<tbody> |
||||||
|
{{range $notification := .Notifications}} |
||||||
|
{{$issue := .Issue}} |
||||||
|
{{$repo := .Repository}} |
||||||
|
{{$repoOwner := $repo.MustOwner}} |
||||||
|
<tr id="notification_{{.ID}}"> |
||||||
|
<td class="collapsing" data-href="{{.HTMLURL}}"> |
||||||
|
{{if eq .Status 3}} |
||||||
|
<span class="blue">{{svg "octicon-pin" 16}}</span> |
||||||
|
{{else if $issue.IsPull}} |
||||||
|
{{if $issue.IsClosed}} |
||||||
|
{{if $issue.GetPullRequest.HasMerged}} |
||||||
|
<span class="purple">{{svg "octicon-git-merge" 16}}</span> |
||||||
|
{{else}} |
||||||
|
<span class="red">{{svg "octicon-git-pull-request" 16}}</span> |
||||||
|
{{end}} |
||||||
|
{{else}} |
||||||
|
<span class="green">{{svg "octicon-git-pull-request" 16}}</span> |
||||||
|
{{end}} |
||||||
|
{{else}} |
||||||
|
{{if $issue.IsClosed}} |
||||||
|
<span class="red">{{svg "octicon-issue-closed" 16}}</span> |
||||||
|
{{else}} |
||||||
|
<span class="green">{{svg "octicon-issue-opened" 16}}</span> |
||||||
|
{{end}} |
||||||
|
{{end}} |
||||||
|
</td> |
||||||
|
<td class="eleven wide" data-href="{{.HTMLURL}}"> |
||||||
|
<a class="item" href="{{.HTMLURL}}"> |
||||||
|
#{{$issue.Index}} - {{$issue.Title}} |
||||||
|
</a> |
||||||
|
</td> |
||||||
|
<td data-href="{{AppSubUrl}}/{{$repoOwner.Name}}/{{$repo.Name}}"> |
||||||
|
<a class="item" href="{{AppSubUrl}}/{{$repoOwner.Name}}/{{$repo.Name}}"> |
||||||
|
{{$repoOwner.Name}}/{{$repo.Name}} |
||||||
|
</a> |
||||||
|
</td> |
||||||
|
<td class="collapsing"> |
||||||
|
{{if ne .Status 3}} |
||||||
|
<form action="{{AppSubUrl}}/notifications/status" method="POST"> |
||||||
|
{{$.CsrfTokenHtml}} |
||||||
|
<input type="hidden" name="notification_id" value="{{.ID}}" /> |
||||||
|
<input type="hidden" name="status" value="pinned" /> |
||||||
|
<button class="ui mini button" title='{{$.i18n.Tr "notification.pin"}}' |
||||||
|
data-url="{{AppSubUrl}}/notifications/status" |
||||||
|
data-status="pinned" |
||||||
|
data-page="{{$.Page.Paginater.Current}}" |
||||||
|
data-notification-id="{{.ID}}" |
||||||
|
data-q="{{$.Keyword}}"> |
||||||
|
{{svg "octicon-pin" 16}} |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
{{end}} |
||||||
|
</td> |
||||||
|
<td class="collapsing"> |
||||||
|
{{if or (eq .Status 1) (eq .Status 3)}} |
||||||
|
<form action="{{AppSubUrl}}/notifications/status" method="POST"> |
||||||
|
{{$.CsrfTokenHtml}} |
||||||
|
<input type="hidden" name="notification_id" value="{{.ID}}" /> |
||||||
|
<input type="hidden" name="status" value="read" /> |
||||||
|
<input type="hidden" name="page" value="{{$.Page.Paginater.Current}}" /> |
||||||
|
<button class="ui mini button" title='{{$.i18n.Tr "notification.mark_as_read"}}' |
||||||
|
data-url="{{AppSubUrl}}/notifications/status" |
||||||
|
data-status="read" |
||||||
|
data-page="{{$.Page.Paginater.Current}}" |
||||||
|
data-notification-id="{{.ID}}" |
||||||
|
data-q="{{$.Keyword}}"> |
||||||
|
{{svg "octicon-check" 16}} |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
{{else if eq .Status 2}} |
||||||
|
<form action="{{AppSubUrl}}/notifications/status" method="POST"> |
||||||
|
{{$.CsrfTokenHtml}} |
||||||
|
<input type="hidden" name="notification_id" value="{{.ID}}" /> |
||||||
|
<input type="hidden" name="status" value="unread" /> |
||||||
|
<input type="hidden" name="page" value="{{$.Page.Paginater.Current}}" /> |
||||||
|
<button class="ui mini button" title='{{$.i18n.Tr "notification.mark_as_unread"}}' |
||||||
|
data-url="{{AppSubUrl}}/notifications/status" |
||||||
|
data-status="unread" |
||||||
|
data-page="{{$.Page.Paginater.Current}}" |
||||||
|
data-notification-id="{{.ID}}" |
||||||
|
data-q="{{$.Keyword}}"> |
||||||
|
{{svg "octicon-bell" 16}} |
||||||
|
</button> |
||||||
|
</form> |
||||||
|
{{end}} |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
{{end}} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
{{end}} |
||||||
|
</div> |
||||||
|
{{template "base/paginate" .}} |
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,110 @@ |
|||||||
|
const {AppSubUrl, csrf, NotificationSettings} = window.config; |
||||||
|
|
||||||
|
export function initNotificationsTable() { |
||||||
|
$('#notification_table .button').on('click', async function () { |
||||||
|
const data = await updateNotification( |
||||||
|
$(this).data('url'), |
||||||
|
$(this).data('status'), |
||||||
|
$(this).data('page'), |
||||||
|
$(this).data('q'), |
||||||
|
$(this).data('notification-id'), |
||||||
|
); |
||||||
|
|
||||||
|
$('#notification_div').replaceWith(data); |
||||||
|
initNotificationsTable(); |
||||||
|
await updateNotificationCount(); |
||||||
|
|
||||||
|
return false; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export function initNotificationCount() { |
||||||
|
if (NotificationSettings.MinTimeout <= 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const notificationCount = $('.notification_count'); |
||||||
|
|
||||||
|
if (notificationCount.length > 0) { |
||||||
|
const fn = (timeout, lastCount) => { |
||||||
|
setTimeout(async () => { |
||||||
|
await updateNotificationCountWithCallback(fn, timeout, lastCount); |
||||||
|
}, timeout); |
||||||
|
}; |
||||||
|
|
||||||
|
fn(NotificationSettings.MinTimeout, notificationCount.text()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function updateNotificationCountWithCallback(callback, timeout, lastCount) { |
||||||
|
const currentCount = $('.notification_count').text(); |
||||||
|
if (lastCount !== currentCount) { |
||||||
|
callback(NotificationSettings.MinTimeout, currentCount); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const newCount = await updateNotificationCount(); |
||||||
|
let needsUpdate = false; |
||||||
|
|
||||||
|
if (lastCount !== newCount) { |
||||||
|
needsUpdate = true; |
||||||
|
timeout = NotificationSettings.MinTimeout; |
||||||
|
} else if (timeout < NotificationSettings.MaxTimeout) { |
||||||
|
timeout += NotificationSettings.TimeoutStep; |
||||||
|
} |
||||||
|
|
||||||
|
callback(timeout, newCount); |
||||||
|
|
||||||
|
const notificationDiv = $('#notification_div'); |
||||||
|
if (notificationDiv.length > 0 && needsUpdate) { |
||||||
|
const data = await $.ajax({ |
||||||
|
type: 'GET', |
||||||
|
url: `${AppSubUrl}/notifications?${notificationDiv.data('params')}`, |
||||||
|
data: { |
||||||
|
'div-only': true, |
||||||
|
} |
||||||
|
}); |
||||||
|
notificationDiv.replaceWith(data); |
||||||
|
initNotificationsTable(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function updateNotificationCount() { |
||||||
|
const data = await $.ajax({ |
||||||
|
type: 'GET', |
||||||
|
url: `${AppSubUrl}/api/v1/notifications/new`, |
||||||
|
headers: { |
||||||
|
'X-Csrf-Token': csrf, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const notificationCount = $('.notification_count'); |
||||||
|
if (data.new === 0) { |
||||||
|
notificationCount.addClass('hidden'); |
||||||
|
} else { |
||||||
|
notificationCount.removeClass('hidden'); |
||||||
|
} |
||||||
|
|
||||||
|
notificationCount.text(`${data.new}`); |
||||||
|
|
||||||
|
return `${data.new}`; |
||||||
|
} |
||||||
|
|
||||||
|
async function updateNotification(url, status, page, q, notificationID) { |
||||||
|
if (status !== 'pinned') { |
||||||
|
$(`#notification_${notificationID}`).remove(); |
||||||
|
} |
||||||
|
|
||||||
|
return $.ajax({ |
||||||
|
type: 'POST', |
||||||
|
url, |
||||||
|
data: { |
||||||
|
_csrf: csrf, |
||||||
|
notification_id: notificationID, |
||||||
|
status, |
||||||
|
page, |
||||||
|
q, |
||||||
|
noredirect: true, |
||||||
|
}, |
||||||
|
}); |
||||||
|
} |
Loading…
Reference in new issue