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" .}} |
||||
|
||||
<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 "user/notification/notification_div" .}} |
||||
{{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