* 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" .}} | |||
<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, | |||
}, | |||
}); | |||
} |