* 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>for-closed-social
@ -1,119 +1,3 @@ | |||||
{{template "base/head" .}} | {{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" .}} | {{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, | |||||
}, | |||||
}); | |||||
} |