aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorwinter2025-01-16 20:21:42 +0000
committerwinter2025-01-16 20:21:42 +0000
commitb1d6fbc4d740324d96d7fe2677fb15b9b59d20ea (patch)
treeae2c996cc6a42b3a42437a7ec8812cd184644817
parent8de5608976dc8a73a400267601acb4c8e127de5a (diff)
follow requests
-rw-r--r--Digitigrade/Model/Actor.php12
-rw-r--r--Digitigrade/Model/Session.php10
-rw-r--r--locale/en_GB.json10
-rw-r--r--routes/actor_profile_fragment.php18
-rw-r--r--routes/homepage.php7
-rw-r--r--routes/pending_follows.php10
-rw-r--r--routes/user_auth.php9
-rw-r--r--static/misc.css12
-rw-r--r--static/profile.css9
-rw-r--r--static/skeleton.css24
-rw-r--r--templates/actor_profile.php10
-rw-r--r--templates/actor_profile_pending_follow.php21
-rw-r--r--templates/follow_requests_page.php9
-rw-r--r--templates/side_navigation.php13
-rw-r--r--templates/skeleton.php11
-rw-r--r--templates/unread_indicator.php1
16 files changed, 181 insertions, 5 deletions
diff --git a/Digitigrade/Model/Actor.php b/Digitigrade/Model/Actor.php
index 8a37b1c..d04c841 100644
--- a/Digitigrade/Model/Actor.php
+++ b/Digitigrade/Model/Actor.php
@@ -206,6 +206,18 @@ class Actor extends PushableModel implements RpcReceiver {
}
/**
+ * @return Actor[] a list of actors that would like to follow this actor
+ */
+ public function findPendingFollowers(): array {
+ $relations = FollowRelation::findAllWithObject($this);
+ $relations = array_filter(
+ $relations,
+ fn(FollowRelation $rel) => $rel->status == FollowRelationStatus::PENDING
+ );
+ return array_map(fn(FollowRelation $rel) => $rel->subject, $relations);
+ }
+
+ /**
* @return Actor[] a list of actors that both follow and are followed by this actor
*/
public function findMutualFollows(): array {
diff --git a/Digitigrade/Model/Session.php b/Digitigrade/Model/Session.php
index 31fb8bb..07827c0 100644
--- a/Digitigrade/Model/Session.php
+++ b/Digitigrade/Model/Session.php
@@ -40,4 +40,14 @@ class Session extends Model {
'secure' => GlobalConfig::getInstance()->isHttps()
]);
}
+
+ public function clearCookie() {
+ setcookie('digitigrade-session', '', [
+ 'expires' => (new \DateTimeImmutable('1 year ago'))->getTimestamp(),
+ 'httponly' => true,
+ 'samesite' => 'Lax',
+ 'path' => '/',
+ 'secure' => GlobalConfig::getInstance()->isHttps()
+ ]);
+ }
} \ No newline at end of file
diff --git a/locale/en_GB.json b/locale/en_GB.json
index cb112f5..06ce2a2 100644
--- a/locale/en_GB.json
+++ b/locale/en_GB.json
@@ -7,6 +7,7 @@
"user.profile.requestToFollow.action": "Request to follow",
"user.profile.unfollow.action": "Unfollow",
"user.profile.followsYou": "Follows you",
+ "user.profile.pendingFollowsYou": "Wants to follow you",
"user.notes.placeholder": "This user hasn't posted anything yet.",
"timeline.global": "Global timeline",
"timeline.global.description": "This timeline shows all known public indexable notes.",
@@ -32,6 +33,8 @@
"digitigrade": "Digitigrade",
"note.resharedBy": "Reshared by %s",
"navigation.login": "Log in",
+ "navigation.logout": "Log out",
+ "navigation.followRequests": "Follow requests",
"login.pageTitle": "Log in",
"login.email": "Email address",
"login.password": "Password",
@@ -43,5 +46,10 @@
"writeNote.scope.label": "Visibility",
"writeNote.action": "Post",
"writeReply.label": "Write a reply",
- "writeReply.action": "Reply"
+ "writeReply.action": "Reply",
+ "followRequests.pageTitle": "Pending follow requests",
+ "followRequests.accept.action": "Accept",
+ "followRequests.accepted": "Accepted!",
+ "followRequests.reject.action": "Reject",
+ "followRequests.rejected": "Rejected!"
}
diff --git a/routes/actor_profile_fragment.php b/routes/actor_profile_fragment.php
index 9b864d9..a3be976 100644
--- a/routes/actor_profile_fragment.php
+++ b/routes/actor_profile_fragment.php
@@ -20,4 +20,22 @@ Router::getInstance()->mount('/fragment/actor/:id/followButton', function (array
}
render_template('actor_profile_follow_button', ['user' => $user, 'actor' => $actor]);
+});
+
+Router::getInstance()->mount('/fragment/actor/:id/acceptPending', function (array $args) {
+ $user = UserAccount::requireByCurrentSession();
+ $actor = Actor::find($args['id']);
+
+ $user->actor->acceptPendingFollowFrom($actor);
+
+ render_template('actor_profile_pending_follow', ['response' => 'accepted']);
+});
+
+Router::getInstance()->mount('/fragment/actor/:id/rejectPending', function (array $args) {
+ $user = UserAccount::requireByCurrentSession();
+ $actor = Actor::find($args['id']);
+
+ $user->actor->rejectPendingFollowFrom($actor);
+
+ render_template('actor_profile_pending_follow', ['response' => 'rejected']);
}); \ No newline at end of file
diff --git a/routes/homepage.php b/routes/homepage.php
index db8ea72..c99dce4 100644
--- a/routes/homepage.php
+++ b/routes/homepage.php
@@ -2,12 +2,17 @@
use Digitigrade\HttpResponseStatus\NotFound;
use Digitigrade\HttpResponseStatus\TemporaryRedirect;
+use Digitigrade\Model\UserAccount;
use Digitigrade\Router;
Router::getInstance()->mount('/', function (array $args) {
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
if (str_contains($accept, 'text/html') || str_contains($accept, '*/*')) {
- throw new TemporaryRedirect('/feed/global');
+ throw new TemporaryRedirect(
+ UserAccount::findByCurrentSession() == null
+ ? '/feed/global'
+ : '/feed/home'
+ );
}
throw new NotFound();
}); \ No newline at end of file
diff --git a/routes/pending_follows.php b/routes/pending_follows.php
new file mode 100644
index 0000000..d3bd9da
--- /dev/null
+++ b/routes/pending_follows.php
@@ -0,0 +1,10 @@
+<?php
+
+use Digitigrade\Model\UserAccount;
+use Digitigrade\Router;
+
+Router::getInstance()->mount('/followrequests', function (array $args) {
+ $user = UserAccount::requireByCurrentSession();
+ $actors = $user->actor->findPendingFollowers();
+ render_template('follow_requests_page', ['actors' => $actors]);
+}); \ No newline at end of file
diff --git a/routes/user_auth.php b/routes/user_auth.php
index afdf8d8..49e138b 100644
--- a/routes/user_auth.php
+++ b/routes/user_auth.php
@@ -29,4 +29,13 @@ Router::getInstance()->mount('/login', function (array $args) {
} else {
render_template('login_page', ['email' => $_POST['email'] ?? '', 'failed' => $failedVerification]);
}
+});
+
+Router::getInstance()->mount('/logout', function (array $args) {
+ if (isset($_COOKIE['digitigrade-session'])) {
+ $session = Session::findByToken($_COOKIE['digitigrade-session']);
+ $session->clearCookie();
+ $session->remove();
+ }
+ throw new TemporaryRedirect('/');
}); \ No newline at end of file
diff --git a/static/misc.css b/static/misc.css
index 3281def..9a5c198 100644
--- a/static/misc.css
+++ b/static/misc.css
@@ -59,6 +59,18 @@ span.inlineIcon {
user-select: none;
}
+span.unreadIndicator {
+ background: #3b005e;
+ color: #fff;
+ width: 24px;
+ height: 24px;
+ border-radius: 12px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
+}
+
[hidden] {
display: none;
}
diff --git a/static/profile.css b/static/profile.css
index f3f8ead..3912e82 100644
--- a/static/profile.css
+++ b/static/profile.css
@@ -26,9 +26,16 @@
.followInfo {
display: grid;
- justify-content: right;
+ justify-content: end;
align-items: baseline;
grid-auto-flow: column;
gap: 16px;
+
+ .pendingInfo {
+ display: grid;
+ align-items: baseline;
+ grid-auto-flow: column;
+ gap: 16px;
+ }
}
}
diff --git a/static/skeleton.css b/static/skeleton.css
index a7b7009..bd68c93 100644
--- a/static/skeleton.css
+++ b/static/skeleton.css
@@ -1,4 +1,3 @@
-html,
body {
background: #fdf5ff;
color: #0f070f;
@@ -11,6 +10,7 @@ body {
justify-items: center;
min-height: 100vh;
width: 100%;
+ overflow-y: scroll;
}
header {
@@ -75,6 +75,28 @@ nav {
}
}
+ul.sideNavigation {
+ border: 1px solid #3334;
+ border-radius: 8px;
+ margin: 16px;
+ padding: 0;
+ background: #f8e9ff5e;
+ list-style: none;
+ > li a {
+ display: grid;
+ grid-template-columns: max-content 1fr max-content;
+ align-items: center;
+ justify-content: start;
+ padding: 16px;
+ gap: 16px;
+ color: inherit;
+ text-decoration: none;
+ }
+ > li:not(:last-child) {
+ border-bottom: 1px solid #3334;
+ }
+}
+
main {
width: min(1300px, 100%);
display: grid;
diff --git a/templates/actor_profile.php b/templates/actor_profile.php
index c480d21..0dc3ccd 100644
--- a/templates/actor_profile.php
+++ b/templates/actor_profile.php
@@ -5,7 +5,13 @@ $user = UserAccount::findByCurrentSession();
?>
<div class="fullProfile">
<div class="basicInfo">
- <?php call_template('actor_avatar', ['actor' => $actor]); ?>
+ <?php if ($addLink ?? false): ?>
+ <a href="/@/<?= $actor->getFullHandle() ?>">
+ <?php endif; ?>
+ <?php call_template('actor_avatar', ['actor' => $actor]); ?>
+ <?php if ($addLink ?? false): ?>
+ </a>
+ <?php endif; ?>
<div>
<div class="displayName"><?= $actor->displayName ?></div>
<div class="handle">@<?= $actor->getFullHandle() ?></div>
@@ -26,6 +32,8 @@ $user = UserAccount::findByCurrentSession();
<?php if (isset($user) && $actor->id != $user->actor->id): ?>
<div class="followInfo">
+ <?php if ($actor->followsPending($user->actor))
+ call_template('actor_profile_pending_follow', ['subject' => $actor, 'object' => $user->actor]); ?>
<?php if ($actor->follows($user->actor)): ?>
<?= __('user.profile.followsYou') ?>
<?php endif; ?>
diff --git a/templates/actor_profile_pending_follow.php b/templates/actor_profile_pending_follow.php
new file mode 100644
index 0000000..2f49fcd
--- /dev/null
+++ b/templates/actor_profile_pending_follow.php
@@ -0,0 +1,21 @@
+<?php if (isset($response)): ?>
+
+ <div class="pendingInfo">
+ <?= __("followRequests.$response") ?>
+ </div>
+
+<?php else: ?>
+
+ <div class="pendingInfo">
+ <?= __('user.profile.pendingFollowsYou') ?>
+ <button class="primary" hx-post="/fragment/actor/<?= $subject->id ?>/acceptPending" hx-target="closest .pendingInfo"
+ hx-swap="outerHTML">
+ <?= __('followRequests.accept.action') ?>
+ </button>
+ <button class="secondary" hx-post="/fragment/actor/<?= $subject->id ?>/rejectPending"
+ hx-target="closest .pendingInfo" hx-swap="outerHTML">
+ <?= __('followRequests.reject.action') ?>
+ </button>
+ </div>
+
+<?php endif; ?> \ No newline at end of file
diff --git a/templates/follow_requests_page.php b/templates/follow_requests_page.php
new file mode 100644
index 0000000..f93601c
--- /dev/null
+++ b/templates/follow_requests_page.php
@@ -0,0 +1,9 @@
+<?php call_template('skeleton', ['pageTitle' => __('followRequests.pageTitle')], function () {
+ global $actors;
+ if (count($actors) == 0) {
+ call_template('placeholder_text');
+ }
+ foreach ($actors as $actor) {
+ call_template('actor_profile', ['actor' => $actor, 'addLink' => true]);
+ }
+}); \ No newline at end of file
diff --git a/templates/side_navigation.php b/templates/side_navigation.php
new file mode 100644
index 0000000..d9a2dfd
--- /dev/null
+++ b/templates/side_navigation.php
@@ -0,0 +1,13 @@
+<ul class="sideNavigation">
+ <?php foreach ($links as $entry): ?>
+ <li>
+ <a href="<?= $entry['href'] ?>">
+ <div class="icon material-symbols-outlined"><?= $entry['icon'] ?></div>
+ <div><?= $entry['label'] ?></div>
+ <?php if (isset($entry['unreadCount']) && $entry['unreadCount'] > 0) {
+ call_template('unread_indicator', ['count' => $entry['unreadCount']]);
+ } ?>
+ </a>
+ </li>
+ <?php endforeach; ?>
+</ul> \ No newline at end of file
diff --git a/templates/skeleton.php b/templates/skeleton.php
index 6eb6bd1..fced6b8 100644
--- a/templates/skeleton.php
+++ b/templates/skeleton.php
@@ -1,4 +1,6 @@
<?php
+
+use Digitigrade\Model\FollowRelation;
$user = Digitigrade\Model\UserAccount::findByCurrentSession();
?>
<!DOCTYPE html>
@@ -44,6 +46,15 @@ $user = Digitigrade\Model\UserAccount::findByCurrentSession();
<?php
if ($user != null) {
call_template('write_note_form');
+ call_template('side_navigation', ['links' => [
+ [
+ 'href' => '/followrequests',
+ 'icon' => 'person_add',
+ 'label' => __('navigation.followRequests'),
+ 'unreadCount' => FollowRelation::countWhere("object = ? AND status = 'pending'", [$user->actor->id])
+ ],
+ ['href' => '/logout', 'icon' => 'logout', 'label' => __('navigation.logout')]
+ ]]);
}
?>
</section>
diff --git a/templates/unread_indicator.php b/templates/unread_indicator.php
new file mode 100644
index 0000000..bacacc3
--- /dev/null
+++ b/templates/unread_indicator.php
@@ -0,0 +1 @@
+<span class="unreadIndicator"><?= $count ?></span> \ No newline at end of file