diff options
author | Yorhel <git@yorhel.nl> | 2021-09-30 17:23:52 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2021-09-30 19:04:56 +0200 |
commit | 595ed7f6512803faca15e0a7c60d0c8e2b524e00 (patch) | |
tree | 65b1285f978fd3662584846b18f2330344ff9f70 /lib | |
parent | d78d00e0622f631153ae650f26e882fdb6eed961 (diff) |
Use TableOpts for user lists
This is primarily in preparation to add card/grid view to user lists,
but also comes with shorter URLs and an option to select how many rows
to display.
This change has the potential to break stuff, I hope I've managed to
limit the breakage somehow.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/VNWeb/TableOpts.pm | 20 | ||||
-rw-r--r-- | lib/VNWeb/ULists/Elm.pm | 7 | ||||
-rw-r--r-- | lib/VNWeb/ULists/List.pm | 280 |
3 files changed, 192 insertions, 115 deletions
diff --git a/lib/VNWeb/TableOpts.pm b/lib/VNWeb/TableOpts.pm index fa976f40..30f5f52b 100644 --- a/lib/VNWeb/TableOpts.pm +++ b/lib/VNWeb/TableOpts.pm @@ -118,6 +118,12 @@ sub tableopts { } +# COMPAT: For old URLs, we assume that this validation is used on the 's' +# parameter, so we can accept two formats: +# - "s=$compat_sort_column/$order" +# - "s=$compat_sort_column&o=$order" +# In the latter case, the validation will use reqGet() to get the 'o' +# parameter. TUWF::set('custom_validations')->{tableopts} = sub { my($t) = @_; +{ onerror => sub { @@ -125,11 +131,12 @@ TUWF::set('custom_validations')->{tableopts} = sub { bless([$d // $t->{default},$t], __PACKAGE__) }, func => sub { my $obj = bless [undef, $t], __PACKAGE__; - my $col = [grep $_->{compat} && $_->{compat} eq $_[0], values $t->{columns}->%*]->[0]; + my($val,$ord) = $_[0] =~ m{^([^/]+)/([ad])$} ? ($1,$2) : ($_[0],undef); + my $col = [grep $_->{compat} && $_->{compat} eq $val, values $t->{columns}->%*]->[0]; if($col && defined $col->{sort_id}) { $obj->[0] = $t->{default}; $obj->set_sort_col_id($col->{sort_id}); - my $ord = tuwf->reqGet('o'); + $ord //= tuwf->reqGet('o'); $obj->set_order($ord && $ord eq 'd' ? 1 : 0); } else { $obj->[0] = _dec($_[0]) // return 0; @@ -189,6 +196,15 @@ sub sql_order { # Returns whether the given column key is visible. sub vis { $_[0][0] & (1 << (12+$_[0][1]{columns}{$_[1]}{vis_id})) } +# Given a list of column names, return a new object with only these columns visible +sub vis_param { + my($self, @cols) = @_; + my $n = bless [@$self], __PACKAGE__; + $n->[0] = $n->[0] & 0b1111_1111_1111; + $n->[0] |= 1 << (12+$self->[1]{columns}{$_}{vis_id}) for @cols; + $n; +} + my $FORM_OUT = form_compile any => { save => { required => 0 }, diff --git a/lib/VNWeb/ULists/Elm.pm b/lib/VNWeb/ULists/Elm.pm index c88156df..ab2839e0 100644 --- a/lib/VNWeb/ULists/Elm.pm +++ b/lib/VNWeb/ULists/Elm.pm @@ -235,14 +235,9 @@ elm_api UListWidget => $WIDGET, { uid => { vndbid => 'u' }, vid => { vndbid => ' our %SAVED_OPTS = ( - # Labels l => { onerror => [], type => 'array', scalar => 1, values => { int => 1 } }, mul => { anybool => 1 }, - # Sort column & order - s => { onerror => 'title', enum => [qw[ title label vote voted added modified started finished rel rating ]] }, - o => { onerror => 'a', enum => ['a', 'd'] }, - # Visible columns - c => { onerror => [], type => 'array', scalar => 1, values => { enum => [qw[ label vote voted added modified started finished rel rating ]] } }, + s => { onerror => '' }, # TableOpts query string ); my $SAVED_OPTS = { diff --git a/lib/VNWeb/ULists/List.pm b/lib/VNWeb/ULists/List.pm index 35703aa1..4d279b0a 100644 --- a/lib/VNWeb/ULists/List.pm +++ b/lib/VNWeb/ULists/List.pm @@ -5,24 +5,113 @@ use VNWeb::ULists::Lib; use VNWeb::Releases::Lib; +my $TABLEOPTS = tableopts + title => { + name => 'Title', + sort_sql => 'v.title', + sort_id => 0, + compat => 'title', + sort_default => 'asc', + }, + voted => { + name => 'Vote date', + sort_sql => 'uv.vote_date', + sort_id => 1, + vis_id => 0, + compat => 'voted' + }, + vote => { + name => 'Vote', + sort_sql => 'uv.vote', + sort_id => 2, + vis_id => 1, + compat => 'vote' + }, + rating => { + name => 'Rating', + sort_sql => 'v.c_rating', + sort_id => 3, + vis_id => 2, + compat => 'rating' + }, + label => { + name => 'Labels', + sort_sql => sql('ARRAY(SELECT ul.label FROM ulist_vns_labels uvl JOIN ulist_labels ul ON ul.uid = uvl.uid AND ul.id = uvl.lbl WHERE uvl.uid = uv.uid AND uvl.vid = uv.vid AND uvl.lbl <> ', \7, ')'), + sort_id => 4, + vis_id => 3, + compat => 'label' + }, + added => { + name => 'Added', + sort_sql => 'uv.added', + sort_id => 5, + vis_id => 4, + compat => 'added' + }, + modified => { + name => 'Modified', + sort_sql => 'uv.lastmod', + sort_id => 6, + vis_id => 5, + compat => 'modified' + }, + started => { + name => 'Start date', + sort_sql => 'uv.started', + sort_id => 7, + vis_id => 6, + compat => 'started' + }, + finished => { + name => 'Finish date', + sort_sql => 'uv.finished', + sort_id => 8, + vis_id => 7, + compat => 'finished' + }, + rel => { + name => 'Release date', + sort_sql => 'v.c_released', + sort_id => 9, + vis_id => 8, + compat => 'rel' + }; + + sub opt { my($u, $filtlabels) = @_; + # Note that saved defaults may still use the old query format, which is + # { s => $sort_column, o => $order, c => [$visible_columns] } my sub load { my $o = $u->{"ulist_$_[0]"}; ($o && eval { JSON::XS->new->decode($o) } or {})->%* }; + state $s_default = tuwf->compile({ tableopts => $TABLEOPTS })->validate(undef)->data; + state $s_vnlist = $s_default->sort_param(title => 'a')->vis_param(qw/label vote added started finished/)->query_encode; + state $s_votes = $s_default->sort_param(voted => 'd')->vis_param(qw/vote voted/)->query_encode; + state $s_wishlist = $s_default->sort_param(title => 'a')->vis_param(qw/label added/)->query_encode; + my $opt = # Presets - tuwf->reqGet('vnlist') ? { mul => 0, p => 1, l => [1,2,3,4,7,-1,0], s => 'title', o => 'a', c => [qw/label vote added started finished/], load 'vnlist' } : - tuwf->reqGet('votes') ? { mul => 0, p => 1, l => [7], s => 'voted', o => 'd', c => [qw/vote voted/], load 'votes' } : - tuwf->reqGet('wishlist') ? { mul => 0, p => 1, l => [5], s => 'title', o => 'a', c => [qw/label added/], load 'wish' } : + tuwf->reqGet('vnlist') ? { mul => 0, p => 1, l => [1,2,3,4,7,-1,0], s => $s_vnlist, load 'vnlist' } : + tuwf->reqGet('votes') ? { mul => 0, p => 1, l => [7], s => $s_votes, load 'votes' } : + tuwf->reqGet('wishlist') ? { mul => 0, p => 1, l => [5], s => $s_wishlist, load 'wish' } : # Full options tuwf->validate(get => p => { upage => 1 }, ch=> { onerror => undef, enum => [ 'a'..'z', 0 ] }, q => { onerror => undef }, - %VNWeb::ULists::Elm::SAVED_OPTS + %VNWeb::ULists::Elm::SAVED_OPTS, + # Compat for old URLs + o => { onerror => undef, enum => ['a', 'd'] }, + c => { onerror => undef, type => 'array', scalar => 1, values => { enum => [qw[ label vote voted added modified started finished rel rating ]] } }, )->data; + $opt->{s} .= "/$opt->{o}" if $opt->{o}; + $opt->{s} = tuwf->compile({ tableopts => $TABLEOPTS })->validate($opt->{s})->data; + $opt->{s} = $opt->{s}->vis_param($opt->{c}->@*) if $opt->{c}; + delete $opt->{o}; + delete $opt->{c}; + # $labels only includes labels we are allowed to see, getting rid of any labels in 'l' that aren't in $labels ensures we only filter on visible labels my %accessible_labels = map +($_->{id}, 1), @$filtlabels; my %opt_l = map +($_, 1), grep $accessible_labels{$_}, $opt->{l}->@*; @@ -42,43 +131,38 @@ sub filters_ { txt_ " ($_->{count})"; } - form_ method => 'get', sub { - input_ type => 'hidden', name => 's', value => $opt->{s}; - input_ type => 'hidden', name => 'o', value => $opt->{o}; - input_ type => 'hidden', name => 'ch', value => $opt->{ch} if defined $opt->{ch}; - input_ type => 'hidden', name => 'c', value => $_ for $opt->{c}->@*; - p_ class => 'labelfilters', sub { - input_ type => 'text', class => 'text', name => 'q', value => $opt->{q}||'', style => 'width: 500px', placeholder => 'Search', tabindex => 10; - br_; - # XXX: Rather silly that everything in this form is a form element except for the alphabet filter. Meh, behavior seems intuitive enough. - span_ class => 'browseopts', sub { - a_ href => $url->(ch => $_, p => undef), ($_//'') eq ($opt->{ch}//'') ? (class => 'optselected') : (), !defined($_) ? 'ALL' : $_ ? uc $_ : '#' - for (undef, 'a'..'z', 0); + input_ type => 'hidden', name => 'ch', value => $opt->{ch} if defined $opt->{ch}; + p_ class => 'labelfilters', sub { + input_ type => 'text', class => 'text', name => 'q', value => $opt->{q}||'', style => 'width: 500px', placeholder => 'Search', tabindex => 10; + br_; + # XXX: Rather silly that everything in this form is a form element except for the alphabet filter. Meh, behavior seems intuitive enough. + span_ class => 'browseopts', sub { + a_ href => $url->(ch => $_, p => undef), ($_//'') eq ($opt->{ch}//'') ? (class => 'optselected') : (), !defined($_) ? 'ALL' : $_ ? uc $_ : '#' + for (undef, 'a'..'z', 0); + }; + br_; + span_ class => 'linkradio', sub { + join_ sub { em_ ' / ' }, \&lblfilt_, grep $_->{id} < 10, @$filtlabels; + + span_ class => 'hidden', sub { + em_ ' || '; + input_ type => 'checkbox', name => 'mul', value => 1, id => 'form_l_multi', tabindex => 10, $opt->{mul} ? (checked => 'checked') : (); + label_ for => 'form_l_multi', 'Multi-select'; }; + debug_ $filtlabels; + }; + my @cust = grep $_->{id} >= 10, @$filtlabels; + if(@cust) { br_; span_ class => 'linkradio', sub { - join_ sub { em_ ' / ' }, \&lblfilt_, grep $_->{id} < 10, @$filtlabels; - - span_ class => 'hidden', sub { - em_ ' || '; - input_ type => 'checkbox', name => 'mul', value => 1, id => 'form_l_multi', tabindex => 10, $opt->{mul} ? (checked => 'checked') : (); - label_ for => 'form_l_multi', 'Multi-select'; - }; - debug_ $filtlabels; - }; - my @cust = grep $_->{id} >= 10, @$filtlabels; - if(@cust) { - br_; - span_ class => 'linkradio', sub { - join_ sub { em_ ' / ' }, \&lblfilt_, @cust; - } + join_ sub { em_ ' / ' }, \&lblfilt_, @cust; } - br_; - input_ type => 'submit', class => 'submit', tabindex => 10, value => 'Update filters'; - input_ type => 'button', class => 'submit', tabindex => 10, id => 'managelabels', value => 'Manage labels' if $own; - input_ type => 'button', class => 'submit', tabindex => 10, id => 'savedefault', value => 'Save as default' if $own; - input_ type => 'button', class => 'submit', tabindex => 10, id => 'exportlist', value => 'Export' if $own; - }; + } + br_; + input_ type => 'submit', class => 'submit', tabindex => 10, value => 'Update filters'; + input_ type => 'button', class => 'submit', tabindex => 10, id => 'managelabels', value => 'Manage labels' if $own; + input_ type => 'button', class => 'submit', tabindex => 10, id => 'savedefault', value => 'Save as default' if $own; + input_ type => 'button', class => 'submit', tabindex => 10, id => 'exportlist', value => 'Export' if $own; }; } @@ -89,7 +173,7 @@ sub vn_ { my %labels = map +($_,1), $v->{labels}->@*; td_ class => 'tc1', sub { - input_ type => 'checkbox', class => 'checkhidden', name => 'collapse_vid', id => 'collapse_vid'.$v->{id}, value => 'collapsed_vid'.$v->{id}; + input_ type => 'checkbox', class => 'checkhidden', 'x-checkall' => 'collapse_vid', id => 'collapse_vid'.$v->{id}, value => 'collapsed_vid'.$v->{id}; label_ for => 'collapse_vid'.$v->{id}, sub { my $obtained = grep $_->{status} == 2, $v->{rels}->@*; my $total = $v->{rels}->@*; @@ -108,19 +192,19 @@ sub vn_ { }; }; - td_ class => 'tc_voted', $v->{vote_date} ? fmtdate $v->{vote_date}, 'compact' : '-' if in voted => $opt->{c}; + td_ class => 'tc_voted', $v->{vote_date} ? fmtdate $v->{vote_date}, 'compact' : '-' if $opt->{s}->vis('voted'); td_ mkclass(tc_vote => 1, compact => $own, stealth => $own), sub { txt_ fmtvote $v->{vote} if !$own; elm_ 'UList.VoteEdit' => $VNWeb::ULists::Elm::VNVOTE, { uid => $uid, vid => $v->{id}, vote => fmtvote($v->{vote}) }, sub { div_ @_, fmtvote $v->{vote} } if $own && ($v->{vote} || sprintf('%08d', $v->{c_released}||0) < strftime '%Y%m%d', gmtime); - } if in vote => $opt->{c}; + } if $opt->{s}->vis('vote'); td_ class => 'tc_rating', sub { txt_ sprintf '%.2f', ($v->{c_rating}||0)/100; b_ class => 'grayedout', sprintf ' (%d)', $v->{c_votecount}; - } if in rating => $opt->{c}; + } if $opt->{s}->vis('rating'); td_ class => 'tc_labels', sub { my @l = grep $labels{$_->{id}} && $_->{id} != 7, @$labels; @@ -132,31 +216,31 @@ sub vn_ { } else { txt_ $txt; } - } if in label => $opt->{c}; + } if $opt->{s}->vis('label'); td_ class => 'tc_title', sub { a_ href => "/$v->{id}", title => $v->{original}||$v->{title}, shorten $v->{title}, 70; b_ class => 'grayedout', id => 'ulist_notes_'.$v->{id}, $v->{notes} if $v->{notes} || $own; }; - td_ class => 'tc_added', fmtdate $v->{added}, 'compact' if in added => $opt->{c}; - td_ class => 'tc_modified', fmtdate $v->{lastmod}, 'compact' if in modified => $opt->{c}; + td_ class => 'tc_added', fmtdate $v->{added}, 'compact' if $opt->{s}->vis('added'); + td_ class => 'tc_modified', fmtdate $v->{lastmod}, 'compact' if $opt->{s}->vis('modified'); td_ class => 'tc_started', sub { txt_ $v->{started}||'' if !$own; elm_ 'UList.DateEdit' => $VNWeb::ULists::Elm::VNDATE, { uid => $uid, vid => $v->{id}, date => $v->{started}||'', start => 1 }, sub { div_ @_, $v->{started}||'' } if $own; - } if in started => $opt->{c}; + } if $opt->{s}->vis('started'); td_ class => 'tc_finished', sub { txt_ $v->{finished}||'' if !$own; elm_ 'UList.DateEdit' => $VNWeb::ULists::Elm::VNDATE, { uid => $uid, vid => $v->{id}, date => $v->{finished}||'', start => 0 }, sub { div_ @_, $v->{finished}||'' } if $own; - } if in finished => $opt->{c}; + } if $opt->{s}->vis('finished'); - td_ class => 'tc_rel', sub { rdate_ $v->{c_released} } if in rel => $opt->{c}; + td_ class => 'tc_rel', sub { rdate_ $v->{c_released} } if $opt->{s}->vis('rel'); }; tr_ mkclass(hidden => 1, 'collapsed_vid'.$v->{id} => 1, odd => $n % 2 == 0), sub { @@ -191,7 +275,7 @@ sub listing_ { my $count = tuwf->dbVali('SELECT count(*) FROM ulist_vns uv JOIN vn v ON v.id = uv.vid WHERE', $where); - my $lst = tuwf->dbPagei({ page => $opt->{p}, results => 50 }, + my $lst = tuwf->dbPagei({ page => $opt->{p}, results => $opt->{s}->results }, 'SELECT v.id, v.title, v.original, uv.vote, uv.notes, uv.started, uv.finished, v.c_rating, v.c_votecount, v.c_released ,', sql_totime('uv.added'), ' as added ,', sql_totime('uv.lastmod'), ' as lastmod @@ -199,18 +283,7 @@ sub listing_ { FROM ulist_vns uv JOIN vn v ON v.id = uv.vid WHERE', $where, ' - ORDER BY', { - title => 'v.title', - label => sql('ARRAY(SELECT ul.label FROM ulist_vns_labels uvl JOIN ulist_labels ul ON ul.uid = uvl.uid AND ul.id = uvl.lbl WHERE uvl.uid = uv.uid AND uvl.vid = uv.vid AND uvl.lbl <> ', \7, ')'), - vote => 'uv.vote', - voted => 'uv.vote_date', - added => 'uv.added', - modified => 'uv.lastmod', - started => 'uv.started', - finished => 'uv.finished', - rel => 'v.c_released', - rating => 'v.c_rating', - }->{$opt->{s}}, $opt->{o} eq 'd' ? 'DESC' : 'ASC', 'NULLS LAST, v.title' + ORDER BY', $opt->{s}->sql_order(), 'NULLS LAST, v.title' ); enrich_flatten labels => id => vid => sql('SELECT vid, lbl FROM ulist_vns_labels WHERE uid =', \$uid, 'AND vid IN'), $lst; @@ -226,42 +299,30 @@ sub listing_ { }, $lst; enrich_release_elm map $_->{rels}, @$lst; - # TODO: Thumbnail view? - paginate_ $url, $opt->{p}, [ $count, 50 ], 't', sub { - elm_ ColSelect => 'raw', [ $url->(), [ - [ voted => 'Vote date' ], - [ vote => 'Vote' ], - [ rating => 'Rating' ], - [ label => 'Labels' ], - [ added => 'Added' ], - [ modified => 'Modified' ], - [ started => 'Start date' ], - [ finished => 'Finish date' ], - [ rel => 'Release date' ], - ] ]; - }; + paginate_ $url, $opt->{p}, [$count, $opt->{s}->results], 't', sub { $opt->{s}->elm_ }; div_ class => 'mainbox browse ulist', sub { table_ sub { thead_ sub { tr_ sub { td_ class => 'tc1', sub { - input_ type => 'checkbox', class => 'checkall', name => 'collapse_vid', id => 'collapse_vid'; + # TODO: these checkboxes shouldn't be included in the query string + input_ type => 'checkbox', class => 'checkall', 'x-checkall' => 'collapse_vid', id => 'collapse_vid'; label_ for => 'collapse_vid', sub { txt_ 'Opt' }; }; - td_ class => 'tc_voted', sub { txt_ 'Vote date'; sortable_ 'voted', $opt, $url } if in voted => $opt->{c}; - td_ class => 'tc_vote', sub { txt_ 'Vote'; sortable_ 'vote', $opt, $url } if in vote => $opt->{c}; - td_ class => 'tc_rating', sub { txt_ 'Rating'; sortable_ 'rating', $opt, $url } if in rating => $opt->{c}; - td_ class => 'tc_labels', sub { txt_ 'Labels'; sortable_ 'label', $opt, $url } if in label => $opt->{c}; + td_ class => 'tc_voted', sub { txt_ 'Vote date'; sortable_ 'voted', $opt, $url } if $opt->{s}->vis('voted'); + td_ class => 'tc_vote', sub { txt_ 'Vote'; sortable_ 'vote', $opt, $url } if $opt->{s}->vis('vote'); + td_ class => 'tc_rating', sub { txt_ 'Rating'; sortable_ 'rating', $opt, $url } if $opt->{s}->vis('rating'); + td_ class => 'tc_labels', sub { txt_ 'Labels'; sortable_ 'label', $opt, $url } if $opt->{s}->vis('label'); td_ class => 'tc_title', sub { txt_ 'Title'; sortable_ 'title', $opt, $url; debug_ $lst }; - td_ class => 'tc_added', sub { txt_ 'Added'; sortable_ 'added', $opt, $url } if in added => $opt->{c}; - td_ class => 'tc_modified', sub { txt_ 'Modified'; sortable_ 'modified', $opt, $url } if in modified => $opt->{c}; - td_ class => 'tc_started', sub { txt_ 'Start date'; sortable_ 'started', $opt, $url } if in started => $opt->{c}; - td_ class => 'tc_finished', sub { txt_ 'Finish date'; sortable_ 'finished', $opt, $url } if in finished => $opt->{c}; - td_ class => 'tc_rel', sub { txt_ 'Release date';sortable_ 'rel', $opt, $url } if in rel => $opt->{c}; + td_ class => 'tc_added', sub { txt_ 'Added'; sortable_ 'added', $opt, $url } if $opt->{s}->vis('added'); + td_ class => 'tc_modified', sub { txt_ 'Modified'; sortable_ 'modified', $opt, $url } if $opt->{s}->vis('modified'); + td_ class => 'tc_started', sub { txt_ 'Start date'; sortable_ 'started', $opt, $url } if $opt->{s}->vis('started'); + td_ class => 'tc_finished', sub { txt_ 'Finish date'; sortable_ 'finished', $opt, $url } if $opt->{s}->vis('finished'); + td_ class => 'tc_rel', sub { txt_ 'Release date';sortable_ 'rel', $opt, $url } if $opt->{s}->vis('rel'); }}; vn_ $uid, $own, $opt, $_, $lst->[$_], $labels for (0..$#$lst); }; }; - paginate_ $url, $opt->{p}, [ $count, 50 ], 'b'; + paginate_ $url, $opt->{p}, [$count, $opt->{s}->results], 'b'; } @@ -320,28 +381,33 @@ TUWF::get qr{/$RE{uid}/ulist}, sub { } ) : (), sub { my $empty = !grep $_->{count}, @$filtlabels; - div_ class => 'mainbox', sub { - h1_ $title; - if($empty) { - p_ $own - ? 'Your list is empty! You can add visual novels to your list from the visual novel pages.' - : user_displayname($u).' does not have any visible visual novels in their list.'; - } else { - filters_ $own, $filtlabels, $opt, $opt_labels, \&url; - elm_ 'UList.ManageLabels' if $own; - elm_ 'UList.SaveDefault', $VNWeb::ULists::Elm::SAVED_OPTS_OUT, { uid => $u->{id}, opts => $opt } if $own; - div_ class => 'hidden exportlist', sub { - b_ 'Export your list'; - br_; - txt_ 'This function will export all visual novels and releases in your list, even those marked as private '; - txt_ '(there is currently no import function, more export options may be added later).'; - br_; - br_; - a_ href => "/$u->{id}/list-export/xml", "Download XML export."; - } if $own; - } - }; - listing_ $u->{id}, $own, $opt, $labels, \&url if !$empty; + form_ method => 'get', sub { + div_ class => 'mainbox', sub { + h1_ $title; + if($empty) { + p_ $own + ? 'Your list is empty! You can add visual novels to your list from the visual novel pages.' + : user_displayname($u).' does not have any visible visual novels in their list.'; + } else { + filters_ $own, $filtlabels, $opt, $opt_labels, \&url; + elm_ 'UList.ManageLabels' if $own; + elm_ 'UList.SaveDefault', $VNWeb::ULists::Elm::SAVED_OPTS_OUT, { + uid => $u->{id}, + opts => { l => $opt->{l}, mul => $opt->{mul}, s => $opt->{s}->query_encode() }, + } if $own; + div_ class => 'hidden exportlist', sub { + b_ 'Export your list'; + br_; + txt_ 'This function will export all visual novels and releases in your list, even those marked as private '; + txt_ '(there is currently no import function, more export options may be added later).'; + br_; + br_; + a_ href => "/$u->{id}/list-export/xml", "Download XML export."; + } if $own; + } + }; + listing_ $u->{id}, $own, $opt, $labels, \&url if !$empty; + } }; }; |