summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2019-10-12 14:11:06 +0200
committerYorhel <git@yorhel.nl>2019-10-12 14:11:06 +0200
commit0be1aa6acb62c13b4bcb4bd52a9c76df5f248e45 (patch)
tree8bfef25c3a01c740657d2ff2554ad73454d08d27
parent748ae0c68b5f060807aeb5b201f611859c428c79 (diff)
v2rw: Convert user notifications interface (/u+/notifies)
-rw-r--r--data/js/misc.js13
-rw-r--r--data/style.css6
-rw-r--r--elm/global.js12
-rw-r--r--lib/VNDB/DB/Users.pm70
-rw-r--r--lib/VNDB/Handler/Users.pm151
-rw-r--r--lib/VNWeb/Prelude.pm7
-rw-r--r--lib/VNWeb/User/Notifications.pm174
7 files changed, 193 insertions, 240 deletions
diff --git a/data/js/misc.js b/data/js/misc.js
index 8e471012..274b9422 100644
--- a/data/js/misc.js
+++ b/data/js/misc.js
@@ -79,19 +79,6 @@ if(byId('listsel'))
ulist_redirect('[rv]', '/list', this.name, 'e='+this.options[this.selectedIndex].value);
};
-// Notification list onclick
-(function(){
- var d = byId('notifies');
- if(!d)
- return;
- var l = byClass(d, 'td', 'clickable');
- for(var i=0; i<l.length; i++)
- l[i].onclick = function() {
- var baseurl = location.href.replace(/\/u([0-9]+)\/notifies.*$/, '/u$1/notify/');
- location.href = baseurl + this.id.replace(/notify_/, '');
- };
-})();
-
// BBCode spoiler tags
(function(){
diff --git a/data/style.css b/data/style.css
index 87034f90..9ad103a4 100644
--- a/data/style.css
+++ b/data/style.css
@@ -787,9 +787,9 @@ div.votelist td.tc2 { width: 50px; text-align: right; padding-right: 10px }
/***** User notifications *****/
.browse.notifies td.tc1 { width: 14px }
-.browse.notifies td.tc3 { width: 90px }
-.browse.notifies td.tc4 { width: 60px }
-.browse.notifies tbody td.tc5 { color: $grayedout$; cursor: pointer }
+.browse.notifies td.tc3 { width: 100px }
+.browse.notifies td.tc4 { width: 75px }
+.browse.notifies tbody td.tc5 a { color: $grayedout$ }
.browse.notifies td.tc5 i { font-style: normal; color: $maintext$ }
.browse.notifies .unread td { font-weight: bold }
.browse.notifies tfoot td { padding: 0 0 0 25px }
diff --git a/elm/global.js b/elm/global.js
index f1c5444a..d396e0bc 100644
--- a/elm/global.js
+++ b/elm/global.js
@@ -24,3 +24,15 @@ document.querySelectorAll('div[data-elm-module]').forEach(function(el) {
else
mod.init({ node: el });
});
+
+
+/* "check all" checkbox */
+document.querySelectorAll('input[type=checkbox].checkall').forEach(function(el) {
+ el.onclick = function() {
+ document.querySelectorAll('input[type=checkbox][name="'+el.name+'"]').forEach(function(el2) {
+ if(!el2.classList.contains('hidden')) {
+ el2.checked = el.checked;
+ }
+ });
+ };
+});
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm
index a0c209c2..d253a52b 100644
--- a/lib/VNDB/DB/Users.pm
+++ b/lib/VNDB/DB/Users.pm
@@ -7,8 +7,6 @@ use Exporter 'import';
our @EXPORT = qw|
dbUserGet dbUserDel
- dbNotifyGet dbNotifyMarkRead dbNotifyRemove
- dbThrottleGet dbThrottleSet
|;
@@ -88,73 +86,5 @@ sub dbUserDel {
$_[0]->dbExec(q|DELETE FROM users WHERE id = ?|, $_[1]);
}
-
-# %options->{ uid id what results page reverse }
-# what: titles
-sub dbNotifyGet {
- my($s, %o) = @_;
- $o{what} ||= '';
- $o{results} ||= 10;
- $o{page} ||= 1;
-
- my %where = (
- 'n.uid = ?' => $o{uid},
- $o{id} ? (
- 'n.id = ?' => $o{id} ) : (),
- defined($o{read}) ? (
- 'n.read !s' => $o{read} ? 'IS NOT NULL' : 'IS NULL' ) : (),
- );
-
- my @join = (
- $o{what} =~ /titles/ ? 'LEFT JOIN users u ON n.c_byuser = u.id' : (),
- );
-
- my @select = (
- qw|n.id n.ntype n.ltype n.iid n.subid|,
- q|extract('epoch' from n.date) as date|,
- q|extract('epoch' from n.read) as read|,
- $o{what} =~ /titles/ ? ('n.c_title', VNWeb::DB::sql_user()) : (),
- );
-
- my($r, $np) = $s->dbPage(\%o, q|
- SELECT !s
- FROM notifications n
- !s
- !W
- ORDER BY n.id !s
- |, join(', ', @select), join(' ', @join), \%where, $o{reverse} ? 'DESC' : 'ASC');
- return wantarray ? ($r, $np) : $r;
-}
-
-
-# ids
-sub dbNotifyMarkRead {
- my $s = shift;
- $s->dbExec('UPDATE notifications SET read = NOW() WHERE id IN(!l)', \@_);
-}
-
-
-# ids
-sub dbNotifyRemove {
- my $s = shift;
- $s->dbExec('DELETE FROM notifications WHERE id IN(!l)', \@_);
-}
-
-
-# ip
-sub dbThrottleGet {
- my $s = shift;
- my $t = $s->dbRow("SELECT extract('epoch' from timeout) as timeout FROM login_throttle WHERE ip = ?", shift)->{timeout};
- return $t && $t >= time ? $t : time;
-}
-
-# ip, timeout
-sub dbThrottleSet {
- my($s, $ip, $timeout) = @_;
- !$timeout ? $s->dbExec('DELETE FROM login_throttle WHERE ip = ?', $ip)
- : $s->dbExec('UPDATE login_throttle SET timeout = to_timestamp(?) WHERE ip = ?', $timeout, $ip)
- || $s->dbExec('INSERT INTO login_throttle (ip, timeout) VALUES (?, to_timestamp(?))', $ip, $timeout);
-}
-
1;
diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm
index e10fcaa5..933825e7 100644
--- a/lib/VNDB/Handler/Users.pm
+++ b/lib/VNDB/Handler/Users.pm
@@ -15,8 +15,6 @@ TUWF::register(
qr{u([1-9]\d*)/posts} => \&posts,
qr{u([1-9]\d*)/del(/[od])?} => \&delete,
qr{u/(all|[0a-z])} => \&list,
- qr{u([1-9]\d*)/notifies} => \&notifies,
- qr{u([1-9]\d*)/notify/([1-9]\d*)} => \&readnotify,
);
@@ -188,154 +186,5 @@ sub list {
}
-sub notifies {
- my($self, $uid) = @_;
-
- my $u = $self->dbUserGet(uid => $uid)->[0];
- return $self->htmlDenied if !$u->{id} || $uid != $self->authInfo->{id};
-
- my $f = $self->formValidate(
- { get => 'p', required => 0, default => 1, template => 'page' },
- { get => 'r', required => 0, default => 0, enum => [0,1] },
- );
- return $self->resNotFound if $f->{_err};
-
- # changing the notification settings
- my $saved;
- if($self->reqMethod() eq 'POST' && $self->reqPost('set')) {
- return if !$self->authCheckCode;
- my $frm = $self->formValidate(
- { post => 'notify_dbedit', required => 0, default => 0, enum => [0,1] },
- { post => 'notify_announce', required => 0, default => 0, enum => [0,1] }
- );
- return $self->resNotFound if $frm->{_err};
- $self->authPref($_, $frm->{$_}) for ('notify_dbedit', 'notify_announce');
- $saved = 1;
-
- # updating notifications
- } elsif($self->reqMethod() eq 'POST') {
- return if !$self->authCheckCode;
- my $frm = $self->formValidate(
- { post => 'notifysel', multi => 1, required => 0, template => 'id' },
- { post => 'markread', required => 0 },
- { post => 'remove', required => 0 }
- );
- return $self->resNotFound if $frm->{_err};
- my @ids = grep $_, @{$frm->{notifysel}};
- $self->dbNotifyMarkRead(@ids) if @ids && $frm->{markread};
- $self->dbNotifyRemove(@ids) if @ids && $frm->{remove};
- }
-
- my($list, $np) = $self->dbNotifyGet(
- uid => $uid,
- page => $f->{p},
- results => 25,
- what => 'titles',
- read => $f->{r} == 1 ? undef : 0,
- reverse => $f->{r} == 1,
- );
-
- $self->htmlHeader(title => 'My notifications', noindex => 1);
- $self->htmlMainTabs(u => $u);
- div class => 'mainbox';
- h1 'My notifications';
- p class => 'browseopts';
- a !$f->{r} ? (class => 'optselected') : (), href => "/u$uid/notifies?r=0", 'Unread notifications';
- a $f->{r} ? (class => 'optselected') : (), href => "/u$uid/notifies?r=1", 'All notifications';
- end;
- p 'No notifications!' if !@$list;
- end;
-
- my $code = $self->authGetCode("/u$uid/notifies");
-
- my %ntypes = (
- pm => 'Private Message',
- dbdel => 'Entry you contributed to has been deleted',
- listdel => 'VN in your (wish)list has been deleted',
- dbedit => 'Entry you contributed to has been edited',
- announce => 'Site announcement',
- );
-
- if(@$list) {
- form action => "/u$uid/notifies?r=$f->{r};formcode=$code", method => 'post', id => 'notifies';
- $self->htmlBrowse(
- items => $list,
- options => $f,
- nextpage => $np,
- class => 'notifies',
- pageurl => "/u$uid/notifies?r=$f->{r}",
- header => [
- [ '' ],
- [ 'Type' ],
- [ 'Age' ],
- [ 'ID' ],
- [ 'Action' ],
- ],
- row => sub {
- my($s, $n, $l) = @_;
- Tr $l->{read} ? () : (class => 'unread');
- td class => 'tc1';
- input type => 'checkbox', name => 'notifysel', value => "$l->{id}";
- end;
- td class => 'tc2', $ntypes{$l->{ntype}};
- td class => 'tc3', fmtage $l->{date};
- td class => 'tc4';
- a href => "/u$uid/notify/$l->{id}", "$l->{ltype}$l->{iid}".($l->{subid}?".$l->{subid}":'');
- end;
- td class => 'tc5 clickable', id => "notify_$l->{id}";
- txt $l->{ltype} eq 't' ? 'Edit of ' : $l->{subid} == 1 ? 'New thread ' : 'Reply to ';
- i $l->{c_title};
- txt ' by ';
- i VNWeb::HTML::user_displayname($l);
- end;
- end 'tr';
- },
- footer => sub {
- Tr;
- td colspan => 5;
- input type => 'checkbox', class => 'checkall', name => 'notifysel', value => 0;
- txt ' ';
- input type => 'submit', name => 'markread', value => 'mark selected read';
- input type => 'submit', name => 'remove', value => 'remove selected';
- b class => 'grayedout', ' (Read notifications are automatically removed after one month)';
- end;
- end;
- }
- );
- end;
- }
-
- form method => 'post', action => "/u$uid/notifies?formcode=$code";
- div class => 'mainbox';
- h1 'Settings';
- div class => 'notice', 'Settings successfully saved.' if $saved;
- p;
- for('dbedit', 'announce') {
- input type => 'checkbox', name => "notify_$_", id => "notify_$_", value => 1,
- $self->authPref("notify_$_") ? (checked => 'checked') : ();
- label for => "notify_$_", $_ eq 'dbedit'
- ? ' Notify me about edits of database entries I contributed to.'
- : ' Notify me about site announcements.';
- br;
- }
- input type => 'submit', name => 'set', value => 'Save';
- end;
- end;
- end 'form';
- $self->htmlFooter;
-}
-
-
-sub readnotify {
- my($self, $uid, $nid) = @_;
- return $self->htmlDenied if !$self->authInfo->{id} || $uid != $self->authInfo->{id};
- my $n = $self->dbNotifyGet(uid => $uid, id => $nid)->[0];
- return $self->resNotFound if !$n->{iid};
- $self->dbNotifyMarkRead($n->{id}) if !$n->{read};
- # NOTE: for t+.+ IDs, this will create a double redirect, which is rather awkward...
- $self->resRedirect("/$n->{ltype}$n->{iid}".($n->{subid}?".$n->{subid}":''), 'perm');
-}
-
-
1;
diff --git a/lib/VNWeb/Prelude.pm b/lib/VNWeb/Prelude.pm
index cfcd0031..66f78425 100644
--- a/lib/VNWeb/Prelude.pm
+++ b/lib/VNWeb/Prelude.pm
@@ -13,7 +13,7 @@
# use VNDB::BBCode;
# use VNDB::Types;
# use VNDB::Config;
-# use VNDB::Func 'fmtdate', 'fmtvote';
+# use VNDB::Func 'fmtdate', 'fmtage', 'fmtvote';
# use VNWeb::Auth;
# use VNWeb::HTML;
# use VNWeb::DB;
@@ -55,7 +55,7 @@ sub import {
use VNDB::BBCode;
use VNDB::Types;
use VNDB::Config;
- use VNDB::Func 'fmtdate', 'fmtvote';
+ use VNDB::Func 'fmtdate', 'fmtage', 'fmtvote';
use VNWeb::Auth;
use VNWeb::HTML;
use VNWeb::DB;
@@ -71,10 +71,11 @@ sub import {
# Regular expressions for use in path registration
-my $num = qr{[1-9][0-9]{0,6}};
+my $num = qr{[1-9][0-9]{0,8}};
my $id = qr{(?<id>$num)};
my $rev = qr{(?:\.(?<rev>$num))};
our %RE = (
+ num => qr{(?<num>$num)},
uid => qr{u$id},
vid => qr{v$id},
rid => qr{r$id},
diff --git a/lib/VNWeb/User/Notifications.pm b/lib/VNWeb/User/Notifications.pm
new file mode 100644
index 00000000..f37fc7ca
--- /dev/null
+++ b/lib/VNWeb/User/Notifications.pm
@@ -0,0 +1,174 @@
+package VNWeb::User::Notifications;
+
+use VNWeb::Prelude;
+
+my %ntypes = (
+ pm => 'Private Message',
+ dbdel => 'Entry you contributed to has been deleted',
+ listdel => 'VN in your (wish)list has been deleted',
+ dbedit => 'Entry you contributed to has been edited',
+ announce => 'Site announcement',
+);
+
+
+sub settings_ {
+ my $id = shift;
+
+ h1_ 'Settings';
+ form_ action => "/u$id/notify_options", method => 'POST', sub {
+ input_ type => 'hidden', class => 'hidden', name => 'csrf', value => auth->csrftoken;
+ p_ sub {
+ label_ sub {
+ input_ type => 'checkbox', name => 'dbedit', auth->pref('notify_dbedit') ? (checked => 'checked') : ();
+ txt_ ' Notify me about edits of database entries I contributed to.';
+ };
+ br_;
+ label_ sub {
+ input_ type => 'checkbox', name => 'announce', auth->pref('notify_announce') ? (checked => 'checked') : ();
+ txt_ ' Notify me about site announcements.';
+ };
+ br_;
+ input_ type => 'submit', class => 'submit', value => 'Save';
+ }
+ };
+}
+
+
+sub listing_ {
+ my($id, $opt, $count, $list) = @_;
+
+ my sub url { "/u$id/notifies?r=$opt->{r}&p=$_" }
+
+ my sub tbl_ {
+ thead_ sub { tr_ sub {
+ td_ '';
+ td_ 'Type';
+ td_ 'Age';
+ td_ 'ID';
+ td_ 'Action';
+ }};
+ tfoot_ sub { tr_ sub {
+ td_ colspan => 5, sub {
+ input_ type => 'checkbox', class => 'checkall', name => 'notifysel', value => 0;
+ txt_ ' ';
+ input_ type => 'submit', class => 'submit', name => 'markread', value => 'mark selected read';
+ input_ type => 'submit', class => 'submit', name => 'remove', value => 'remove selected';
+ b_ class => 'grayedout', ' (Read notifications are automatically removed after one month)';
+ }
+ }};
+ tr_ $_->{read} ? () : (class => 'unread'), sub {
+ my $l = $_;
+ my $lid = $l->{ltype}.$l->{iid}.($l->{subid}?'.'.$l->{subid}:'');
+ my $url = "/u$id/notify/$l->{id}/$lid";
+ td_ class => 'tc1', sub { input_ type => 'checkbox', name => 'notifysel', value => $l->{id}; };
+ td_ class => 'tc2', $ntypes{$l->{ntype}};
+ td_ class => 'tc3', fmtage $l->{date};
+ td_ class => 'tc4', sub { a_ href => $url, $lid };
+ td_ class => 'tc5', sub {
+ a_ href => $url, sub {
+ txt_ $l->{ltype} eq 't' ? 'Edit of ' : $l->{subid} == 1 ? 'New thread ' : 'Reply to ';
+ i_ $l->{c_title};
+ txt_ ' by ';
+ i_ user_displayname $l;
+ };
+ };
+ } for @$list;
+ }
+
+ form_ action => "/u$id/notify_update", method => 'POST', sub {
+ input_ type => 'hidden', class => 'hidden', name => 'url', value => do { local $_ = $opt->{p}; url };
+ paginate_ \&url, $opt->{p}, [$count, 25], 't';
+ div_ class => 'mainbox browse notifies', sub {
+ table_ class => 'stripe', \&tbl_;
+ };
+ paginate_ \&url, $opt->{p}, [$count, 25], 'b';
+ } if $count;
+}
+
+
+TUWF::get qr{/$RE{uid}/notifies}, sub {
+ my $id = tuwf->capture('id');
+ return tuwf->resNotFound if !auth || $id != auth->uid;
+
+ my $opt = eval { tuwf->validate(get =>
+ p => { page => 1 },
+ r => { anybool => 1 },
+ )->data } || { p => 1, r => 0 };
+
+ my $where = sql_and(
+ sql('uid =', \$id),
+ $opt->{r} ? () : 'read IS NULL'
+ );
+ my $count = tuwf->dbVali('SELECT count(*) FROM notifications WHERE', $where);
+ my($list) = tuwf->dbPagei({ results => 25, page => $opt->{p} },
+ 'SELECT n.id, n.ntype, n.ltype, n.iid, n.subid, n.c_title
+ , ', sql_totime('n.date'), ' as date
+ , ', sql_totime('n.read'), ' as read
+ , ', sql_user(),
+ 'FROM notifications n
+ LEFT JOIN users u ON u.id = n.c_byuser
+ WHERE ', $where,
+ 'ORDER BY n.id', $opt->{r} ? 'DESC' : 'ASC'
+ );
+
+ framework_ title => 'My notifications', index => 0,
+ sub {
+ div_ class => 'mainbox', sub {
+ h1_ 'My notifications';
+ p_ class => 'browseopts', sub {
+ a_ !$opt->{r} ? (class => 'optselected') : (), href => '?r=0', 'Unread notifications';
+ a_ $opt->{r} ? (class => 'optselected') : (), href => '?r=1', 'All notifications';
+ };
+ p_ 'No notifications!' if !$count;
+ };
+ listing_ $id, $opt, $count, $list;
+ div_ class => 'mainbox', sub { settings_ $id };
+ };
+};
+
+
+TUWF::post qr{/$RE{uid}/notify_options}, sub {
+ my $id = tuwf->capture('id');
+ return tuwf->resNotFound if !auth || $id != auth->uid;
+
+ my $frm = tuwf->validate(post =>
+ csrf => {},
+ dbedit => { anybool => 1 },
+ announce => { anybool => 1 },
+ )->data;
+ return tuwf->resNotFound if !auth->csrfcheck($frm->{csrf});
+
+ auth->prefSet(notify_dbedit => $frm->{dbedit});
+ auth->prefSet(notify_announce => $frm->{announce});
+ tuwf->resRedirect("/u$id/notifies", 'post');
+};
+
+
+TUWF::post qr{/$RE{uid}/notify_update}, sub {
+ my $id = tuwf->capture('id');
+ return tuwf->resNotFound if !auth || $id != auth->uid;
+
+ my $frm = tuwf->validate(post =>
+ url => { regex => qr{^/u$id/notifies} },
+ notifysel => { required => 0, type => 'array', scalar => 1, values => { id => 1 } },
+ markread => { anybool => 1 },
+ remove => { anybool => 1 },
+ )->data;
+
+ if($frm->{notifysel}->@*) {
+ my $where = sql 'uid =', \$id, ' AND id IN', $frm->{notifysel};
+ tuwf->dbExeci('DELETE FROM notifications WHERE', $where) if $frm->{remove};
+ tuwf->dbExeci('UPDATE notifications SET read = NOW() WHERE', $where) if $frm->{markread};
+ }
+ tuwf->resRedirect($frm->{url}, 'post');
+};
+
+
+TUWF::get qr{/$RE{uid}/notify/$RE{num}/(?<lid>[a-z0-9\.]+)}, sub {
+ my $id = tuwf->capture('id');
+ return tuwf->resNotFound if !auth || $id != auth->uid;
+ tuwf->dbExeci('UPDATE notifications SET read = NOW() WHERE uid =', \$id, ' AND id =', \tuwf->capture('num'));
+ tuwf->resRedirect('/'.tuwf->capture('lid'), 'temp');
+};
+
+1;