diff options
author | Derf Null <derf@finalrewind.org> | 2023-06-04 18:21:36 +0200 |
---|---|---|
committer | Derf Null <derf@finalrewind.org> | 2023-06-04 18:21:36 +0200 |
commit | 00eb6af1bd21df42fc41195ceed0fad73bbb5f27 (patch) | |
tree | aea171b00a79681e403292b0ad06b01e5d9a21d1 | |
parent | 07fe4ecd1f73e7b111d4cd6f6bb8fd390b5f3151 (diff) |
expose follows / social interaction in frontend
-rwxr-xr-x | lib/Travelynx.pm | 7 | ||||
-rw-r--r-- | lib/Travelynx/Command/database.pm | 21 | ||||
-rw-r--r-- | lib/Travelynx/Controller/Account.pm | 254 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Profile.pm | 100 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 1 | ||||
-rw-r--r-- | lib/Travelynx/Model/Users.pm | 106 | ||||
-rw-r--r-- | templates/_checked_in.html.ep | 9 | ||||
-rw-r--r-- | templates/_format_train.html.ep | 9 | ||||
-rw-r--r-- | templates/_public_status_card.html.ep | 39 | ||||
-rw-r--r-- | templates/account.html.ep | 95 | ||||
-rw-r--r-- | templates/edit_visibility.html.ep | 15 | ||||
-rw-r--r-- | templates/layouts/default.html.ep | 12 | ||||
-rw-r--r-- | templates/privacy.html.ep | 22 | ||||
-rw-r--r-- | templates/profile.html.ep | 73 | ||||
-rw-r--r-- | templates/social.html.ep | 66 | ||||
-rw-r--r-- | templates/social_list.html.ep | 230 |
16 files changed, 984 insertions, 75 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index adb6132..6dd77fd 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -399,6 +399,9 @@ sub startup { if ( $visibility eq 'travelynx' ) { return 'lock_open'; } + if ( $visibility eq 'followers' ) { + return 'group'; + } if ( $visibility eq 'unlisted' ) { return 'lock_outline'; } @@ -2213,6 +2216,8 @@ sub startup { $authed_r->get('/account')->to('account#account'); $authed_r->get('/account/privacy')->to('account#privacy'); + $authed_r->get('/account/social')->to('account#social'); + $authed_r->get('/account/social/:kind')->to('account#social_list'); $authed_r->get('/account/profile')->to('account#profile'); $authed_r->get('/account/hooks')->to('account#webhook'); $authed_r->get('/account/traewelling')->to('traewelling#settings'); @@ -2240,6 +2245,7 @@ sub startup { $authed_r->get('/s/*station')->to('traveling#station'); $authed_r->get('/confirm_mail/:token')->to('account#confirm_mail'); $authed_r->post('/account/privacy')->to('account#privacy'); + $authed_r->post('/account/social')->to('account#social'); $authed_r->post('/account/profile')->to('account#profile'); $authed_r->post('/account/hooks')->to('account#webhook'); $authed_r->post('/account/traewelling')->to('traewelling#settings'); @@ -2254,6 +2260,7 @@ sub startup { $authed_r->post('/account/password')->to('account#change_password'); $authed_r->post('/account/mail')->to('account#change_mail'); $authed_r->post('/account/name')->to('account#change_name'); + $authed_r->post('/social-action')->to('account#social_action'); $authed_r->post('/delete')->to('account#delete'); $authed_r->post('/logout')->to('account#do_logout'); $authed_r->post('/set_token')->to('api#set_token'); diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 53168bf..3878502 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -1545,6 +1545,27 @@ my @migrations = ( } ); }, + + # v37 -> v38 + sub { + my ($db) = @_; + $db->query( + qq{ + drop view followers; + create view followers as select + relations.object_id as self_id, + users.id as id, + users.name as name, + users.accept_follows as accept_follows, + r2.predicate as inverse_predicate + from relations + join users on relations.subject_id = users.id + left join relations as r2 on relations.subject_id = r2.object_id + where relations.predicate = 1; + update schema_version set version = 38; + } + ); + }, ); # TODO add 'hafas' column to in_transit (and maybe journeys? undo/redo needs something to work with...) diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index af97c96..fe5d5cc 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -408,11 +408,8 @@ sub delete { my ($self) = @_; my $uid = $self->current_user->{id}; if ( $self->validation->csrf_protect->has_error('csrf_token') ) { - $self->render( - 'account', - api_token => $self->users->get_api_token( uid => $uid ), - invalid => 'csrf', - ); + $self->flash( invalid => 'csrf' ); + $self->redirect_to('account'); return; } @@ -424,11 +421,8 @@ sub delete { ) ) { - $self->render( - 'account', - api_token => $self->users->get_api_token( uid => $uid ), - invalid => 'deletion password' - ); + $self->flash( invalid => 'deletion password' ); + $self->redirect_to('account'); return; } $self->users->flag_deletion( uid => $uid ); @@ -501,6 +495,228 @@ sub privacy { } } +sub social { + my ($self) = @_; + + my $user = $self->current_user; + + if ( $self->param('action') and $self->param('action') eq 'save' ) { + if ( $self->validation->csrf_protect->has_error('csrf_token') ) { + $self->render( + 'social', + invalid => 'csrf', + ); + return; + } + + my %opt; + my $accept_follow = $self->param('accept_follow'); + + if ( $accept_follow eq 'yes' ) { + $opt{accept_follows} = 1; + } + elsif ( $accept_follow eq 'request' ) { + $opt{accept_follow_requests} = 1; + } + + $self->users->set_social( + uid => $user->{id}, + %opt + ); + + $self->flash( success => 'social' ); + $self->redirect_to('account'); + } + else { + if ( $user->{accept_follows} ) { + $self->param( accept_follow => 'yes' ); + } + elsif ( $user->{accept_follow_requests} ) { + $self->param( accept_follow => 'request' ); + } + else { + $self->param( accept_follow => 'no' ); + } + $self->render( 'social', name => $user->{name} ); + } +} + +sub social_list { + my ($self) = @_; + + my $kind = $self->stash('kind'); + my $user = $self->current_user; + + if ( $kind eq 'follow-requests' ) { + my @follow_reqs + = $self->users->get_follow_requests( uid => $user->{id} ); + $self->render( + 'social_list', + type => 'follow-requests', + entries => [@follow_reqs], + notifications => $user->{notifications}, + ); + } + elsif ( $kind eq 'followers' ) { + my @followers = $self->users->get_followers( uid => $user->{id} ); + $self->render( + 'social_list', + type => 'followers', + entries => [@followers], + notifications => $user->{notifications}, + ); + } + elsif ( $kind eq 'follows' ) { + my @following = $self->users->get_followees( uid => $user->{id} ); + $self->render( + 'social_list', + type => 'follows', + entries => [@following], + notifications => $user->{notifications}, + ); + } + elsif ( $kind eq 'blocks' ) { + my @blocked = $self->users->get_blocked_users( uid => $user->{id} ); + $self->render( + 'social_list', + type => 'blocks', + entries => [@blocked], + notifications => $user->{notifications}, + ); + } + else { + $self->render( 'not_found', status => 404 ); + } +} + +sub social_action { + my ($self) = @_; + + my $user = $self->current_user; + my $action = $self->param('action'); + my $target_ids = $self->param('target'); + my $redirect_to = $self->param('redirect_to'); + + for my $key ( + qw(follow request_follow follow_or_request unfollow remove_follower cancel_follow_request accept_follow_request reject_follow_request block unblock) + ) + { + if ( $self->param($key) ) { + $action = $key; + $target_ids = $self->param($key); + } + } + + if ( $self->validation->csrf_protect->has_error('csrf_token') ) { + $self->redirect_to('/'); + return; + } + + if ( $action and $action eq 'clear_notifications' ) { + $self->users->update_notifications( + db => $self->pg->db, + uid => $user->{id}, + has_follow_requests => 0 + ); + $self->flash( success => 'clear_notifications' ); + $self->redirect_to('account'); + return; + } + + if ( not( $action and $target_ids and $redirect_to ) ) { + $self->redirect_to('/'); + return; + } + + for my $target_id ( split( qr{,}, $target_ids ) ) { + my $target = $self->users->get_privacy_by( uid => $target_id ); + + if ( not $target ) { + next; + } + + if ( $action eq 'follow' and $target->{accept_follows} ) { + $self->users->follow( + uid => $user->{id}, + target => $target->{id} + ); + } + elsif ( $action eq 'request_follow' + and $target->{accept_follow_requests} ) + { + $self->users->request_follow( + uid => $user->{id}, + target => $target->{id} + ); + } + elsif ( $action eq 'follow_or_request' ) { + if ( $target->{accept_follows} ) { + $self->users->follow( + uid => $user->{id}, + target => $target->{id} + ); + } + elsif ( $target->{accept_follow_requests} ) { + $self->users->request_follow( + uid => $user->{id}, + target => $target->{id} + ); + } + } + elsif ( $action eq 'unfollow' ) { + $self->users->unfollow( + uid => $user->{id}, + target => $target->{id} + ); + } + elsif ( $action eq 'remove_follower' ) { + $self->users->remove_follower( + uid => $user->{id}, + follower => $target->{id} + ); + } + elsif ( $action eq 'cancel_follow_request' ) { + $self->users->cancel_follow_request( + uid => $user->{id}, + target => $target->{id} + ); + } + elsif ( $action eq 'accept_follow_request' ) { + $self->users->accept_follow_request( + uid => $user->{id}, + applicant => $target->{id} + ); + } + elsif ( $action eq 'reject_follow_request' ) { + $self->users->reject_follow_request( + uid => $user->{id}, + applicant => $target->{id} + ); + } + elsif ( $action eq 'block' ) { + $self->users->block( + uid => $user->{id}, + target => $target->{id} + ); + } + elsif ( $action eq 'unblock' ) { + $self->users->unblock( + uid => $user->{id}, + target => $target->{id} + ); + } + + if ( $redirect_to eq 'profile' ) { + + # profile links do not perform bulk actions + $self->redirect_to( '/p/' . $target->{name} ); + return; + } + } + + $self->redirect_to($redirect_to); +} + sub profile { my ($self) = @_; my $user = $self->current_user; @@ -1012,11 +1228,21 @@ sub confirm_mail { } sub account { - my ($self) = @_; - my $uid = $self->current_user->{id}; + my ($self) = @_; + my $uid = $self->current_user->{id}; + my $follow_requests = $self->users->has_follow_requests( uid => $uid ); + my $followers = $self->users->has_followers( uid => $uid ); + my $following = $self->users->has_followees( uid => $uid ); + my $blocked = $self->users->has_blocked_users( uid => $uid ); - $self->render( 'account', - api_token => $self->users->get_api_token( uid => $uid ) ); + $self->render( + 'account', + api_token => $self->users->get_api_token( uid => $uid ), + num_follow_requests => $follow_requests, + num_followers => $followers, + num_following => $following, + num_blocked => $blocked, + ); $self->users->mark_seen( uid => $uid ); } diff --git a/lib/Travelynx/Controller/Profile.pm b/lib/Travelynx/Controller/Profile.pm index 86b8922..b11dd71 100755 --- a/lib/Travelynx/Controller/Profile.pm +++ b/lib/Travelynx/Controller/Profile.pm @@ -186,6 +186,23 @@ sub profile { metadata => $profile->{metadata}, public_level => $user->{public_level}, is_self => $is_self, + following => ( $relation and $relation eq 'follows' ) ? 1 : 0, + follow_requested => ( $relation and $relation eq 'requests_follow' ) + ? 1 + : 0, + can_follow => ( $my_user and $user->{accept_follows} and not $relation ) + ? 1 + : 0, + can_request_follow => + ( $my_user and $user->{accept_follow_requests} and not $relation ) + ? 1 + : 0, + follows_me => ( $inverse_relation and $inverse_relation eq 'follows' ) + ? 1 + : 0, + follow_reqs_me => + ( $inverse_relation and $inverse_relation eq 'requests_follow' ) ? 1 + : 0, journey => $status, journey_visibility => $visibility, journeys => [@journeys], @@ -201,6 +218,24 @@ sub journey_details { $self->param( journey_id => $journey_id ); + my $my_user; + my $relation; + my $inverse_relation; + my $is_self; + if ( $self->is_user_authenticated ) { + $my_user = $self->current_user; + if ( $my_user->{id} == $user->{id} ) { + $is_self = 1; + $my_user = undef; + } + else { + $relation = $self->users->get_relation( + subject => $my_user->{id}, + object => $user->{id} + ); + } + } + if ( not( $user and $journey_id and $journey_id =~ m{ ^ \d+ $ }x ) ) { $self->render( 'journey', @@ -249,7 +284,12 @@ sub journey_details { and $self->journey_token_ok($journey) ) or ( $visibility eq 'travelynx' - and ( ( $self->is_user_authenticated and not $is_past ) + and ( ( $my_user and not $is_past ) + or $self->journey_token_ok($journey) ) + ) + or ( + $visibility eq 'followers' + and ( ( $relation and $relation eq 'follows' ) or $self->journey_token_ok($journey) ) ) ) @@ -337,6 +377,24 @@ sub user_status { return; } + my $my_user; + my $relation; + my $inverse_relation; + my $is_self; + if ( $self->is_user_authenticated ) { + $my_user = $self->current_user; + if ( $my_user->{id} == $user->{id} ) { + $is_self = 1; + $my_user = undef; + } + else { + $relation = $self->users->get_relation( + subject => $my_user->{id}, + object => $user->{id} + ); + } + } + my $status = $self->get_user_status( $user->{id} ); if ( @@ -364,7 +422,12 @@ sub user_status { and $self->journey_token_ok( $journey, $ts ) ) or ( $visibility eq 'travelynx' - and ( $self->is_user_authenticated + and ( $my_user + or $self->journey_token_ok( $journey, $ts ) ) + ) + or ( + $visibility eq 'followers' + and ( ( $relation and $relation eq 'follows' ) or $self->journey_token_ok( $journey, $ts ) ) ) ) @@ -408,7 +471,12 @@ sub user_status { and $self->status_token_ok( $status, $ts ) ) or ( $visibility eq 'travelynx' - and ( $self->is_user_authenticated + and ( $my_user + or $self->status_token_ok( $status, $ts ) ) + ) + or ( + $visibility eq 'followers' + and ( ( $relation and $relation eq 'follows' ) or $self->status_token_ok( $status, $ts ) ) ) ) @@ -486,6 +554,24 @@ sub status_card { return; } + my $my_user; + my $relation; + my $inverse_relation; + my $is_self; + if ( $self->is_user_authenticated ) { + $my_user = $self->current_user; + if ( $my_user->{id} == $user->{id} ) { + $is_self = 1; + $my_user = undef; + } + else { + $relation = $self->users->get_relation( + subject => $my_user->{id}, + object => $user->{id} + ); + } + } + my $status = $self->get_user_status( $user->{id} ); my $visibility; if ( $status->{checked_in} or $status->{arr_name} ) { @@ -500,7 +586,12 @@ sub status_card { and $self->status_token_ok($status) ) or ( $visibility eq 'travelynx' - and ( $self->is_user_authenticated + and ( $my_user + or $self->status_token_ok($status) ) + ) + or ( + $visibility eq 'followers' + and ( ( $relation and $relation eq 'follows' ) or $self->status_token_ok($status) ) ) ) @@ -523,6 +614,7 @@ sub status_card { public_level => $user->{public_level}, journey => $status, journey_visibility => $visibility, + from_profile => $self->param('profile') ? 1 : 0, ); } diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 27005e8..5483e00 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -1459,6 +1459,7 @@ sub journey_details { if ( $visibility eq 'public' or $visibility eq 'travelynx' + or $visibility eq 'followers' or $visibility eq 'unlisted' ) { my $delay = 'pünktlich '; diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 7326e34..ade9711 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -180,13 +180,14 @@ sub get_privacy_by { my $res = $db->select( 'users', - [ 'id', 'public_level', 'accept_follows' ], + [ 'id', 'name', 'public_level', 'accept_follows' ], { %where, status => 1 } ); if ( my $user = $res->hash ) { return { id => $user->{id}, + name => $user->{name}, public_level => $user->{public_level}, # todo remove? default_visibility => $user->{public_level} & 0x7f, default_visibility_str => @@ -777,16 +778,16 @@ sub get_profile { sub get_relation { my ( $self, %opt ) = @_; - my $db = $opt{db} // $self->{pg}->db; - my $uid = $opt{uid}; - my $target = $opt{target}; + my $db = $opt{db} // $self->{pg}->db; + my $subject = $opt{subject}; + my $object = $opt{object}; my $res_h = $db->select( 'relations', ['predicate'], { - subject_id => $uid, - object_id => $target + subject_id => $subject, + object_id => $object, } )->hash; @@ -824,6 +825,24 @@ sub update_notifications { $db->update( 'users', { notifications => $notifications }, { id => $uid } ); } +sub follow { + my ( $self, %opt ) = @_; + + my $db = $opt{db} // $self->{pg}->db; + my $uid = $opt{uid}; + my $target = $opt{target}; + + $db->insert( + 'relations', + { + subject_id => $uid, + predicate => $predicate_atoi{follows}, + object_id => $target, + ts => DateTime->now( time_zone => 'Europe/Berlin' ), + } + ); +} + sub request_follow { my ( $self, %opt ) = @_; @@ -920,6 +939,16 @@ sub reject_follow_request { } } +sub cancel_follow_request { + my ( $self, %opt ) = @_; + + $self->reject_follow_request( + db => $opt{db}, + uid => $opt{target}, + applicant => $opt{uid}, + ); +} + sub unfollow { my ( $self, %opt ) = @_; @@ -1005,9 +1034,50 @@ sub get_followers { my $db = $opt{db} // $self->{pg}->db; my $uid = $opt{uid}; - my $res = $db->select( 'followers', [ 'id', 'name' ], { self_id => $uid } ); + my $res = $db->select( + 'followers', + [ 'id', 'name', 'accept_follows', 'inverse_predicate' ], + { self_id => $uid } + ); + + my @ret; + while ( my $row = $res->hash ) { + push( + @ret, + { + id => $row->{id}, + name => $row->{name}, + following_back => ( + $row->{inverse_predicate} + and $row->{inverse_predicate} == $predicate_atoi{follows} + ) ? 1 : 0, + followback_requested => ( + $row->{inverse_predicate} + and $row->{inverse_predicate} + == $predicate_atoi{requests_follow} + ) ? 1 : 0, + can_follow_back => ( + not $row->{inverse_predicate} + and $row->{accept_follows} == 2 + ) ? 1 : 0, + can_request_follow_back => ( + not $row->{inverse_predicate} + and $row->{accept_follows} == 1 + ) ? 1 : 0, + } + ); + } + return @ret; +} - return $res->hashes->each; +sub has_followers { + my ( $self, %opt ) = @_; + + my $db = $opt{db} // $self->{pg}->db; + my $uid = $opt{uid}; + + return $db->select( 'followers', 'count(*) as count', { self_id => $uid } ) + ->hash->{count}; } sub get_follow_requests { @@ -1043,6 +1113,16 @@ sub get_followees { return $res->hashes->each; } +sub has_followees { + my ( $self, %opt ) = @_; + + my $db = $opt{db} // $self->{pg}->db; + my $uid = $opt{uid}; + + return $db->select( 'followees', 'count(*) as count', { self_id => $uid } ) + ->hash->{count}; +} + sub get_blocked_users { my ( $self, %opt ) = @_; @@ -1055,4 +1135,14 @@ sub get_blocked_users { return $res->hashes->each; } +sub has_blocked_users { + my ( $self, %opt ) = @_; + + my $db = $opt{db} // $self->{pg}->db; + my $uid = $opt{uid}; + + return $db->select( 'blocked_users', 'count(*) as count', + { self_id => $uid } )->hash->{count}; +} + 1; diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep index 2d39842..1002654 100644 --- a/templates/_checked_in.html.ep +++ b/templates/_checked_in.html.ep @@ -3,8 +3,13 @@ <div class="card"> <div class="card-content"> <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> - <span class="card-title"> - Eingecheckt in <%= $journey->{train_type} %> <%= $journey->{train_no} %> + <span class="card-title center-align"> + % if ($journey->{train_line}) { + <b><%= $journey->{train_type} %> <%= $journey->{train_line} %></b> <%= $journey->{train_no} %> + % } + % else { + <%= $journey->{train_type} %> <%= $journey->{train_no} %> + % } </span> % if ($journey->{comment}) { <p><%= $journey->{comment} %></p> diff --git a/templates/_format_train.html.ep b/templates/_format_train.html.ep new file mode 100644 index 0000000..5b61682 --- /dev/null +++ b/templates/_format_train.html.ep @@ -0,0 +1,9 @@ +% if ($journey->{extra_data}{wagonorder_pride}) { + 🏳️🌈 +% } +% if ($journey->{train_line}) { + <b><%= $journey->{train_type} %> <%= $journey->{train_line} %></b> <%= $journey->{train_no} %> +% } +% else { + <%= $journey->{train_type} %> <%= $journey->{train_no} %> +% } diff --git a/templates/_public_status_card.html.ep b/templates/_public_status_card.html.ep index 94ebf44..8e3eec7 100644 --- a/templates/_public_status_card.html.ep +++ b/templates/_public_status_card.html.ep @@ -1,28 +1,28 @@ -<div class="autorefresh"> +<div class="autorefresh" data-from-profile="<%= stash('from_profile') ? 1 : 0 %>"> % if ($journey->{checked_in}) { <div class="card"> <div class="card-content"> <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> - <span class="card-title"><%= $name %> ist unterwegs - % if ($journey_visibility) { - <i class="material-icons right"><%= visibility_icon($journey_visibility) %></i> - % } + <span class="card-title"> + % if (stash('from_profile')) { + Unterwegs mit <%= include '_format_train', journey => $journey %> + % } + % else { + <a href="/p/<%= $name %>"><%= $name %></a> ist unterwegs + % } + % if ($journey_visibility) { + <i class="material-icons right"><%= visibility_icon($journey_visibility) %></i> + % } </span> % if ($public_level & 0x04 and $journey->{comment}) { <p>„<%= $journey->{comment} %>“</p> % } <p> - <div class="center-align"> - % if ($journey->{train_line}) { - <b><%= $journey->{train_type} %> <%= $journey->{train_line} %></b> <%= $journey->{train_no} %> - % } - % else { - <b><%= $journey->{train_type} %> <%= $journey->{train_no} %></b> - % } - % if ($journey->{extra_data}{wagonorder_pride}) { - 🏳️🌈 - % } - </div> + % if (not stash('from_profile')) { + <div class="center-align"> + %= include '_format_train', journey => $journey + </div> + % } <div class="center-align countdown" data-duration="<%= $journey->{journey_duration} // 0 %>" % if (param('token')) { @@ -176,7 +176,12 @@ <div class="card"> <div class="card-content"> <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> - <span class="card-title"><%= $name %> ist gerade nicht eingecheckt</span> + % if (stash('from_profile')) { + <span class="card-title">Aktuell nicht eingecheckt</span> + % } + % else { + <span class="card-title"><a href="/p/<%= $name %>"><%= $name %></a> ist gerade nicht eingecheckt</span> + % } <p> % if ($journey->{arr_name}) { Zuletzt gesehen diff --git a/templates/account.html.ep b/templates/account.html.ep index ef6b847..bb03c7b 100644 --- a/templates/account.html.ep +++ b/templates/account.html.ep @@ -1,4 +1,4 @@ -% if (my $invalid = stash('invalid')) { +% if (my $invalid = flash('invalid')) { %= include '_invalid_input', invalid => $invalid % } @@ -19,6 +19,9 @@ % elsif ($success eq 'privacy') { <span class="card-title">Einstellungen zu öffentlichen Account-Daten geändert</span> % } + % elsif ($success eq 'social') { + <span class="card-title">Einstellungen zur Interaktionen mit anderen Accounts geändert</span> + % } % elsif ($success eq 'traewelling') { <span class="card-title">Träwelling-Verknüpfung aktualisiert</span> % } @@ -31,6 +34,9 @@ % elsif ($success eq 'webhook') { <span class="card-title">Web Hook aktualisiert</span> % } + % elsif ($success eq 'clear_notifications') { + <span class="card-title">Benachrichtigungen gelesen</span> + % } </div> </div> </div> @@ -78,6 +84,27 @@ </td> </tr> <tr> + <th scope="row">Interaktion</th> + <td> + <a href="/account/social"><i class="material-icons">edit</i></a> + % if ($acc->{accept_follows}) { + <span>Accounts können dir direkt folgen</span> + % } + % elsif ($acc->{accept_follow_requests}) { + <span>Accounts können dir auf Anfrage folgen + % if ($num_follow_requests == 1) { + – <a href="/account/social/follow-requests"><strong>eine</strong> offene Anfrage</a> + % } elsif ($num_follow_requests) { + – <a href="/account/social/follow-requests"><strong><%= $num_follow_requests %></strong> offene Anfragen</a> + % } + </span> + % } + % else { + <span style="color: #999999;">Accounts können dir nicht folgen</span> + % } + </td> + </tr> + <tr> <th scope="row">Web Hook</th> <td> <a href="/account/hooks"><i class="material-icons">edit</i></a> @@ -152,6 +179,72 @@ </div> </div> +% if ($num_follow_requests or $num_followers or $num_following or $num_blocked) { + <div class="row"> + <div class="col s12"> + <h2>Interaktion</h2> + <table class="striped"> + <tr> + <th scope="row">Anfragen</th> + <td> + % if ($num_follow_requests == 0) { + <span style="color: #999999;">keine offen</span> + % } + % elsif ($num_follow_requests == 1) { + <a href="/account/social/follow-requests"><strong>ein</strong> Account</a> + % } + % else { + <a href="/account/social/follow-requests"><strong><%= $num_follow_requests %></strong> Accounts</a> + % } + </td> + </tr> + <tr> + <th scope="row">Dir folg<%= $num_followers == 1 ? 't' : 'en' %></th> + <td> + % if ($num_followers == 0) { + <span style="color: #999999;">keine Accounts</span> + % } + % elsif ($num_followers == 1) { + <a href="/account/social/followers"><strong>ein</strong> Account</a> + % } + % else { + <a href="/account/social/followers"><strong><%= $num_followers %></strong> Accounts</a> + % } + </td> + </tr> + <tr> + <th scope="row">Du folgst</th> + <td> + % if ($num_following == 0) { + <span style="color: #999999;">keinen Accounts</span> + % } + % elsif ($num_following == 1) { + <a href="/account/social/follows"><strong>einem</strong> Account</a> + % } + % else { + <a href="/account/social/follows"><strong><%= $num_following %></strong> Accounts</a> + % } + </td> + </tr> + <tr> + <th scope="row">Blockiert</th> + <td> + % if ($num_blocked == 0) { + <span style="color: #999999;">keine Accounts</span> + % } + % elsif ($num_blocked == 1) { + <a href="/account/social/blocks"><strong>ein</strong> Account</a> + % } + % else { + <a href="/account/social/blocks"><strong><%= $num_blocked %></strong> Accounts</a> + % } + </td> + </tr> + </table> + </div> + </div> +% } + % my $token = stash('api_token') // {}; <div class="row"> <div class="col s12"> diff --git a/templates/edit_visibility.html.ep b/templates/edit_visibility.html.ep index 28b1426..9bf8d56 100644 --- a/templates/edit_visibility.html.ep +++ b/templates/edit_visibility.html.ep @@ -40,7 +40,10 @@ Die Fahrt ist öffentlich sichtbar. % } % elsif ($user_level eq 'travelynx') { - Die Fahrt ist nur für auf dieser Seite angemeldete Personen oder mit Link sichtbar. + Die Fahrt ist nur für auf dieser Seite angemeldete Accounts oder mit Link sichtbar. + % } + % elsif ($user_level eq 'followers') { + Die Fahrt ist nur für dir folgende Accounts oder mit Link sichtbar. % } % elsif ($user_level eq 'unlisted') { Die Fahrt ist nur mit Link sichtbar. @@ -68,29 +71,27 @@ <div> <label> %= radio_button status_level => 'travelynx' - <span><i class="material-icons left"><%= visibility_icon('travelynx') %></i>Lokal: Nur für<!-- Personen, die dir folgen oder auf dieser Seite angemeldet sind --> auf dieser Seite angemeldete Acounts sowie nicht angemeldete Personen, denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + <span><i class="material-icons left"><%= visibility_icon('travelynx') %></i>Intern: Personen, die dir folgen, die auf dieser Seite angemeldet sind oder denen du mithilfe der Teilen-Funktion einen Link schickst.</span> </label> </div> </div> </div> - <!-- <div class="row"> <div class="input-field col s12"> <div> <label> - %= radio_button status_level => 'fedi' - <span><i class="material-icons left"><%= visibility_icon('fedi') %></i>Fedi: nur für Personen, die deinem Account folgen.</span> + %= radio_button status_level => 'followers' + <span><i class="material-icons left"><%= visibility_icon('followers') %></i>Follower: Personen, die dir folgen oder denen du mithilfe der Teilen-Funktion einen Link schickst.</span> </label> </div> </div> </div> - --> <div class="row"> <div class="input-field col s12"> <div> <label> %= radio_button status_level => 'unlisted' - <span><i class="material-icons left"><%= visibility_icon('unlisted') %></i>Fast privat: Nur für Personen zugänglich, denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + <span><i class="material-icons left"><%= visibility_icon('unlisted') %></i>Verlinkbar: Personen, denen du mithilfe der Teilen-Funktion einen Link schickst.</span> </label> </div> </div> diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep index bae0fcc..51608d5 100644 --- a/templates/layouts/default.html.ep +++ b/templates/layouts/default.html.ep @@ -70,6 +70,7 @@ %= javascript "/static/${av}/leaflet/leaflet.js" % } </head> +% my $acc = is_user_authenticated() && current_user(); <body> <div class="navbar-fixed"> @@ -93,9 +94,9 @@ <li class="waves-effect waves-light"> <a onClick="javascript:toggleTheme()" title="Farbschema invertieren"><i class="material-icons" aria-label="Farbschema invertieren">invert_colors</i></a> </li> - % if (is_user_authenticated()) { + % if ($acc) { <li class="<%= navbar_class('/history') %>"><a href='/history' title="Vergangene Zugfahrten"><i class="material-icons" aria-label="Vergangene Zugfahrten">history</i></a></li> - <li class="<%= navbar_class('/account') %>"><a href="/account" title="Account"><i class="material-icons" aria-label="Account">account_circle</i></a></li> + <li class="<%= navbar_class('/account') %>"><a href="/account" title="Account"><i class="material-icons" aria-label="Account"><%= $acc->{notifications} ? 'notifications' : 'account_circle' %></i></a></li> % } % else { <li class="<%= navbar_class('/about') %>"><a href='/about' title="Über Travelynx"><i class="material-icons" aria-label="Über Travelynx">info_outline</i></a></li> @@ -127,11 +128,8 @@ % } <div class="container"> - % if (is_user_authenticated()) { - % my $acc = current_user(); - % if ($acc and $acc->{deletion_requested}) { - %= include '_deletion_note', timestamp => $acc->{deletion_requested} - % } + % if ($acc and $acc->{deletion_requested}) { + %= include '_deletion_note', timestamp => $acc->{deletion_requested} % } %= content </div> diff --git a/templates/privacy.html.ep b/templates/privacy.html.ep index 57e3890..3f1d1d5 100644 --- a/templates/privacy.html.ep +++ b/templates/privacy.html.ep @@ -1,8 +1,8 @@ <h1>Öffentliche Daten</h1> <div class="row"> <div class="col s12"> - Hier kannst du auswählen, welche Personengruppen deine Fahrten sowie dein - Profil bei travelynx einsehen können. Zusätzlich kannst du die + Hier kannst du auswählen, welche Personengruppen deine Fahrten + bei travelynx einsehen können. Zusätzlich kannst du die Sichtbarkeit vergangener Fahrten auf die letzten vier Wochen einschränken. Nach dem Einchecken in einen Zug hast du im Checkin-Fenster die Möglichkeit, für die aktuelle Fahrt eine abweichende Sichtbarkeit @@ -27,29 +27,27 @@ <div> <label> %= radio_button status_level => 'travelynx' - <span><i class="material-icons left"><%= visibility_icon('travelynx') %></i>Lokal: Nur für<!-- Personen, die dir folgen oder auf dieser Seite angemeldet sind --> auf dieser Seite angemeldete Acounts sowie nicht angemeldete Personen, denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + <span><i class="material-icons left"><%= visibility_icon('travelynx') %></i>Intern: Personen, die dir folgen, die auf dieser Seite angemeldet sind oder denen du mithilfe der Teilen-Funktion einen Link schickst.</span> </label> </div> </div> </div> - <!-- <div class="row"> <div class="input-field col s12"> <div> <label> - %= radio_button status_level => 'fedi' - <span><i class="material-icons left"><%= visibility_icon('fedi') %></i>Fedi: nur für Personen, die deinem Account folgen.</span> + %= radio_button status_level => 'followers' + <span><i class="material-icons left"><%= visibility_icon('followers') %></i>Follower: Personen, die dir folgen oder denen du mithilfe der Teilen-Funktion einen Link schickst.</span> </label> </div> </div> </div> - --> <div class="row"> <div class="input-field col s12"> <div> <label> %= radio_button status_level => 'unlisted' - <span><i class="material-icons left"><%= visibility_icon('unlisted') %></i>Verlinkbar: Nur für Personen zugänglich, denen du mithilfe der Teilen-Funktion einen Link schickst.</span> + <span><i class="material-icons left"><%= visibility_icon('unlisted') %></i>Verlinkbar: Personen, denen du mithilfe der Teilen-Funktion einen Link schickst.</span> </label> </div> </div> @@ -83,7 +81,7 @@ wird angegeben, dass du gerade nicht eingecheckt seist. </div> </div> -<h2>Profil</h2> +<h2>Vergangene Fahrten</h2> <div class="row"> <div class="input-field col s12"> <div> @@ -111,10 +109,10 @@ Fahrten, die über die Standardeinstellung (siehe oben) oder per individueller Einstellung für die aufrufende Person sichtbar sind, werden hier verlinkt. Derzeit werden nur die letzten zehn Fahrten - angezeigt; dies kann sich in Zukunft ändern. + angezeigt; in Zukunft wird dies ggf. auf sämtliche Fahrten im + gewählten Zeitraum erweitert. </div> </div> -<h2>Vergangenheit</h2> <div class="row"> <div class="input-field col s12"> <div> @@ -133,7 +131,7 @@ <div class="row"> <div class="col s12"> Hier kannst du auswählen, ob alle deiner vergangenen Fahrten für - Profil und Detailseiten in Frage kommen, oder nur die letzten vier + Profil und Detailseiten in Frage kommen oder nur die letzten vier Wochen zugänglich sein sollen. Sofern du sie auf die letzten vier Wochen beschränkst, sind ältere Fahrten nur über einen mit Hilfe des „Teilen“-Knopfs erstellten Links zugänglich. diff --git a/templates/profile.html.ep b/templates/profile.html.ep index 63bc3ff..06f8cfe 100644 --- a/templates/profile.html.ep +++ b/templates/profile.html.ep @@ -11,14 +11,81 @@ </div> % } <div class="row"> - <div class="col s12 publicstatuscol" data-user="<%= $name %>"> - %= include '_public_status_card', name => $name, public_level => $public_level, journey => $journey, journey_visibility => $journey_visibility + <div class="col s12"> + <div class="card"> + <div class="card-content"> + <span class="card-title"><%= $name %> + % if ($following and $follows_me) { + <i class="material-icons right">group</i> + % } + % elsif ($follow_reqs_me) { + <span class="right"> + <a href="/account/social/follow-requests"><i class="material-icons right">notifications</i></a> + </span> + % } + % elsif ($is_self) { + <a href="/account/profile"><i class="material-icons right">edit</i></a> + % } + </span> + % if ($bio) { + %== $bio + % } + % if (@{$metadata // []}) { + <table class="striped"> + % for my $entry (@{$metadata}) { + <tr> + <th scope="row"><%= $entry->{key} %></th> + <td scope="row"><%== $entry->{value}{html} %></td> + </tr> + % } + </table> + % } + </div> + % if ($following or $follow_requested or $can_follow or $can_request_follow) { + <div class="card-action <%= ($can_follow or $can_request_follow) ? 'right-align' : q{} %>"> + %= form_for "/social-action" => (method => 'POST') => begin + %= csrf_field + %= hidden_field target => $uid + %= hidden_field redirect_to => 'profile' + % if ($following) { + <button class="btn-flat waves-effect waves-light" type="submit" name="action" value="unfollow"> + Nicht mehr folgen + </button> + % } + % elsif ($follow_requested) { + <button class="btn-flat waves-effect waves-light" type="submit" name="action" value="cancel_follow_request"> + Folge-Anfrage zurücknehmen + </button> + % } + % elsif ($can_follow or $can_request_follow) { + <button class="btn-flat waves-effect waves-light" type="submit" name="action" value="follow_or_request"> + <i class="material-icons left" aria-hidden="true">person_add</i> + % if ($follows_me) { + Zurückfolgen + % } + % else { + Folgen + % } + % if ($can_request_follow) { + anfragen + % } + </button> + % } + %= end + </div> + % } + </div> + </div> +</div> +<div class="row"> + <div class="col s12 publicstatuscol" data-user="<%= $name %>" data-profile="1"> + %= include '_public_status_card', name => $name, public_level => $public_level, journey => $journey, journey_visibility => $journey_visibility, from_profile => 1 </div> </div> % if ($journeys and @{$journeys}) { <div class="row"> <div class="col s12"> - <h2>Letzte Fahrten von <%= $name %></h1> + <h2>Vergangene Fahrten</h2> </div> </div> %= include '_history_trains', date_format => '%d.%m.%Y', link_prefix => "/p/${name}/j/", journeys => $journeys; diff --git a/templates/social.html.ep b/templates/social.html.ep new file mode 100644 index 0000000..2743abf --- /dev/null +++ b/templates/social.html.ep @@ -0,0 +1,66 @@ +<h1>Interaktion</h1> +<div class="row"> + <div class="col s12"> + Hier kannst du einstellen, ob und wie dir Accounts folgen können. + Ebenfalls kannst du bestehende Verbindungen und Folge-Anfragen + bearbeiten. Die hier vorgenommenen Einstellungen haben keinen Einfluss + darauf, ob/wie du anderen Accounts folgen kannst. + </div> +</div> +%= form_for '/account/social' => (method => 'POST') => begin +%= csrf_field +<h2>Folgen</h2> + <div class="row"> + <div class="input-field col s12"> + <p> + Accounts die dir folgen können alle Checkins sehen, die nicht als privat oder nur mit Link zugänglich vermerkt sind. + Später werden sie zusätzlich die Möglichkeit haben, deinen aktuellen Checkin (sofern sichtbar) als Teil einer Übersicht über die Checkins aller gefolgten Accounts direkt anzusehen (analog zur Timeline im Fediverse). + </p> + <p> + Du hast jederzeit die Möglichkeit, Accounts aus deiner Followerliste zu entfernen, Folge-Anfragen abzulehnen oder Accounts zu blockieren, so dass sie dir weder folgen noch neue Folge-Anfragen stellen können. + </p> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button accept_follow => 'yes' + <span>Andere Accounts können dir direkt (ohne eine Anfrage zu stellen) folgen.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button accept_follow => 'request' + <span>Andere Accounts können dir Folge-Anfragen stellen. Du musst sie explizit annehmen, bevor sie dir folgen.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="input-field col s12"> + <div> + <label> + %= radio_button accept_follow => 'no' + <span>Accounts können dir nicht folgen. Accounts, die dir bereits folgen, werden hiervon nicht berührt.</span> + </label> + </div> + </div> + </div> + <div class="row"> + <div class="col s3 m3 l3"> + </div> + <div class="col s6 m6 l6 center-align"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="save"> + Speichern + <i class="material-icons right">send</i> + </button> + </div> + <div class="col s3 m3 l3"> + </div> + </div> +%= end diff --git a/templates/social_list.html.ep b/templates/social_list.html.ep new file mode 100644 index 0000000..ed8e92a --- /dev/null +++ b/templates/social_list.html.ep @@ -0,0 +1,230 @@ +%= form_for "/social-action" => (method => 'POST') => begin +%= csrf_field +%= hidden_field redirect_to => '/account' +% my $count = scalar @{$entries}; +% if ($type eq 'follow-requests') { + <div class="row"> + <div class="col s12"> + <h2>Folge-Anfragen</h2> + </div> + </div> + <div class="row center-align"> + <div class="col s4"> + <i class="material-icons">block</i><br/> Blockieren + </div> + <div class="col s4"> + <i class="material-icons">cancel</i><br/> Ablehnen + </div> + <div class="col s4"> + <i class="material-icons">check</i><br/> Annehmen + </div> + </div> + % if ($notifications) { + <div class="row center-align"> + <div class="col s12"> + <button class="btn waves-effect waves-light" type="submit" name="action" value="clear_notifications"> + <i class="material-icons left" aria-hidden="true">notifications_off</i> Als gelesen markieren + </button> + </div> + </div> + % } + <!-- + <div class="row center-align"> + <div class="col s6"> + <button class="btn grey waves-effect waves-light" type="submit" name="reject_follow_request" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">cancel</i> Alle ablehnen + </button> + </div> + <div class="col s6"> + <button class="btn waves-effect waves-light" type="submit" name="accept_follow_request" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">check</i> Alle annehmen + </button> + </div> + </div> + <div class="row center-align"> + <div class="col s6"> + <button class="btn red waves-effect waves-light" type="submit" name="block" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">block</i> Alle blockieren + </button> + </div> + </div> + --> +% } +% elsif ($type eq 'followers') { + <div class="row"> + <div class="col s12"> + % if ($count == 1) { + <h2>Dir folgt ein Account</h2> + % } + % else { + <h2>Dir folgen <%= $count %> Accounts</h2> + % } + </div> + </div> + <div class="row center-align"> + <div class="col s4"> + <i class="material-icons">block</i><br/> Blockieren + </div> + <div class="col s4"> + <i class="material-icons">remove</i><br/> Entfernen + </div> + <div class="col s4"> + <i class="material-icons">person_add</i><br/> Zurückfolgen + </div> + </div> + <div class="row center-align"> + <div class="col s4"> + </div> + <div class="col s4"> + <i class="material-icons">access_time</i><br/> Folgen angefragt + </div> + <div class="col s4"> + <i class="material-icons">group</i><br/> Du folgst diesem Account + </div> + </div> + <!-- + <div class="row center-align"> + <div class="col s6"> + <button class="btn grey waves-effect waves-light" type="submit" name="remove_follower" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">remove</i> Alle entfernen + </button> + </div> + <div class="col s6"> + <button class="btn waves-effect waves-light" type="submit" name="follow_or_request" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">group_add</i> Allen zurückfolgen + </button> + </div> + </div> + <div class="row center-align"> + <div class="col s6"> + <button class="btn red waves-effect waves-light" type="submit" name="block" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">block</i> Alle blockieren + </button> + </div> + </div> + --> +% } +% elsif ($type eq 'follows') { + <div class="row"> + <div class="col s12"> + % if ($count == 1) { + <h2>Du folgst einem Account</h2> + % } + % else { + <h2>Du folgst <%= $count %> Accounts</h2> + % } + </div> + </div> + <div class="row center-align"> + <div class="col s12"> + <i class="material-icons">remove</i><br/> Nicht mehr folgen + </div> + </div> + <!-- + <div class="row center-align"> + <div class="col s12"> + <button class="btn grey waves-effect waves-light" type="submit" name="unfollow" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">remove</i> Alle entfernen + </button> + </div> + </div> + --> +% } +% elsif ($type eq 'blocks') { + <div class="row"> + <div class="col s12"> + <h2>Blockierte Accounts</h2> + <p> + Blockierte Accounts können dir nicht folgen und keine Folge-Anfragen stellen. + Sie haben weiterhin Zugriff auf deine als öffentlich oder travelynx-intern markierten Checkins. + </p> + </div> + </div> + <div class="row center-align"> + <div class="col s12"> + <i class="material-icons">remove</i><br/>Entblockieren + </div> + </div> + <!-- + <div class="row center-align"> + <div class="col s12"> + <button class="btn grey waves-effect waves-light" type="submit" name="unblock" value="<%= join(q{,}, map { $_->{id} } @{$entries}) %>"> + <i class="material-icons left" aria-hidden="true">remove</i> Alle entblockieren + </button> + </div> + </div> + --> +% } +%= end + +<div class="row"> + <div class="col s12"> + %= form_for "/social-action" => (method => 'POST') => begin + %= csrf_field + %= hidden_field redirect_to => "/account/social/$type" + <table class="striped"> + % for my $entry (@{$entries}) { + <tr> + <td><a href="/p/<%= $entry->{name} %>"><%= $entry->{name} %></a></td> + % if ($type eq 'follow-requests') { + <td class="right-align"> + <button class="btn-flat waves-effect waves-light" type="submit" name="block" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="blockieren">block</i> + </button> + </td> + <td class="right-align"> + <button class="btn-flat waves-effect waves-light" type="submit" name="reject_follow_request" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="ablehnen">cancel</i> + </button> + </td> + <td class="right-align"> + <button class="btn-flat waves-effect waves-light" type="submit" name="accept_follow_request" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="annehmen">check</i> + </button> + </td> + % } + % elsif ($type eq 'followers') { + <td class="right-align"> + <button class="btn-flat waves-effect waves-light" type="submit" name="block" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="blockieren">block</i> + </button> + </td> + <td class="right-align"> + <button class="btn-flat waves-effect waves-light" type="submit" name="remove_follower" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="entfernen">remove</i> + </button> + </td> + <td class="right-align"> + % if ($entry->{following_back}) { + <i class="material-icons" aria-label="ihr folgt euch gegenseitig">group</i> + % } + % elsif ($entry->{followback_requested}) { + <i class="material-icons" aria-label="Zurückfolgen angefragt">access_time</i> + % } + % elsif ($entry->{can_follow_back} or $entry->{can_request_follow_back}) { + <button class="btn-flat waves-effect waves-light" type="submit" name="follow_or_request" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="zurückfolgen">person_add</i> + </button> + % } + </td> + % } + % elsif ($type eq 'follows') { + <td class="right-align"> + <button class="btn-flat waves-effect waves-light" type="submit" name="unfollow" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="entfolgen">remove</i> + </button> + </td> + % } + % elsif ($type eq 'blocks') { + <td class="right-align"> + <button class="btn-flat waves-effect waves-light" type="submit" name="unblock" value="<%= $entry->{id} %>"> + <i class="material-icons" aria-label="von Blockliste entefrnen">remove</i> + </button> + </td> + % } + </tr> + % } + </table> + %= end + </div> +</div> |