expose follows / social interaction in frontend

This commit is contained in:
Derf Null 2023-06-04 18:21:36 +02:00
parent 07fe4ecd1f
commit 00eb6af1bd
No known key found for this signature in database
GPG key ID: 19E6E524EBB177BA
16 changed files with 984 additions and 75 deletions

View file

@ -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');

View file

@ -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...)

View file

@ -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 );
}

View file

@ -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,
);
}

View file

@ -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 ';

View file

@ -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 }
);
return $res->hashes->each;
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;
}
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;

View file

@ -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>

View file

@ -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} %>
% }

View file

@ -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

View file

@ -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>
@ -77,6 +83,27 @@
• <a href="/p/<%= $acc->{name} %>">Öffentliches Profil</a>
</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>
@ -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">

View file

@ -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>

View file

@ -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>

View file

@ -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.

View file

@ -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;

66
templates/social.html.ep Normal file
View file

@ -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

View file

@ -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>