Skip to content

Commit cd6c733

Browse files
committed
Implement /now page
1 parent 66c081c commit cd6c733

File tree

16 files changed

+327
-10
lines changed

16 files changed

+327
-10
lines changed

app/Models/ActivityEvent.php

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Support\Carbon;
6+
7+
class ActivityEvent
8+
{
9+
public Carbon $date;
10+
public string $title;
11+
public string $description;
12+
public string $longDescription;
13+
public string $url;
14+
15+
public function __construct(Carbon $date, string $title, string $description, string $longDescription, string $url)
16+
{
17+
$this->date = $date;
18+
$this->title = $title;
19+
$this->description = $description;
20+
$this->longDescription = $longDescription;
21+
$this->url = $url;
22+
}
23+
}

app/Providers/AppServiceProvider.php

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Providers;
44

5+
use App\Services\ActivityService;
56
use Carbon\Carbon;
67
use Illuminate\Support\Facades\File;
78
use Illuminate\Support\ServiceProvider;
@@ -10,6 +11,11 @@
1011

1112
class AppServiceProvider extends ServiceProvider
1213
{
14+
public function register(): void
15+
{
16+
$this->app->singleton('activity', ActivityService::class);
17+
}
18+
1319
public function boot(): void
1420
{
1521
$this->bootCarbon();

app/Services/ActivityService.php

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use App\Models\ActivityEvent;
6+
use Carbon\Carbon;
7+
use Illuminate\Support\Collection;
8+
use Illuminate\Support\Facades\Cache;
9+
use Illuminate\Support\Str;
10+
use Statamic\Entries\Entry;
11+
use Statamic\Facades\Entry as Entries;
12+
13+
class ActivityService
14+
{
15+
/**
16+
* @return Collection<string, ActivityEvent>
17+
*/
18+
public function events(): Collection
19+
{
20+
$lastModificationDate = $this->lastModificationDate();
21+
22+
return Cache::remember(
23+
'events_' . $lastModificationDate->timestamp,
24+
86400, // 24 hours
25+
fn () => $this->getEvents(),
26+
);
27+
}
28+
29+
public function lastModificationDate(): Carbon
30+
{
31+
return collect([
32+
Entries::whereCollection('tasks')->sortByDesc('completion_date')->first()->completion_date,
33+
Entries::whereCollection('tasks')->sortByDesc('publication_date')->first()->publication_date,
34+
Entries::whereCollection('comments')->sortByDesc('publication_date')->first()->publication_date,
35+
Entries::whereCollection('posts')->sortByDesc('publication_date')->first()->publication_date,
36+
])
37+
->sort()
38+
->last();
39+
}
40+
41+
/**
42+
* @return Collection<string,ActivityEvent>
43+
*/
44+
private function getEvents(): Collection
45+
{
46+
$entries = collect(Entries::whereInCollection(['tasks', 'comments', 'posts'])->all());
47+
48+
$this->fillRelations($entries);
49+
50+
return $entries
51+
->flatMap(fn ($entry) => call_user_func([$this, 'createEventsFrom' . Str::studly($entry->value('blueprint'))], $entry))
52+
->sortByDesc->date;
53+
}
54+
55+
private function fillRelations(Collection $entries): void
56+
{
57+
$comments = $entries->where('blueprint', 'comment');
58+
59+
foreach ($entries->where('blueprint', 'task') as $task) {
60+
$taskLink = 'entry::' . $task->id;
61+
$taskComments = $comments
62+
->filter(fn ($comment) => $comment->value('task') === $taskLink)
63+
->sortBy->publication_date
64+
->values();
65+
66+
foreach ($taskComments as $index => $comment) {
67+
$comment->position = $index + 2;
68+
}
69+
70+
$task->totalComments = $taskComments->count();
71+
}
72+
}
73+
74+
/**
75+
* @return array<ActivityEvent>
76+
*/
77+
private function createEventsFromTask(Entry $task): array
78+
{
79+
$url = url($task->url);
80+
81+
$events = [new ActivityEvent(
82+
date: $task->publication_date,
83+
title: "Started \"{$task->title}\"",
84+
description: "Started <a href=\"{$url}\">{$task->title}</a>",
85+
longDescription: "<p>I just started a new task: {$task->title}</p>{$task->content}",
86+
url: $url,
87+
)];
88+
89+
if ($task->completion_date) {
90+
$url .= '#comment-' . $task->totalComments + 2;
91+
92+
$events[] = new ActivityEvent(
93+
date: $task->completion_date,
94+
title: "Completed \"{$task->title}\"",
95+
description: "Completed <a href=\"{$url}\">{$task->title}</a>",
96+
longDescription: "<p>I just completed the task {$task->title}.</p>",
97+
url: $url,
98+
);
99+
}
100+
101+
return $events;
102+
}
103+
104+
/**
105+
* @return array<ActivityEvent>
106+
*/
107+
private function createEventsFromComment(Entry $comment): array
108+
{
109+
$task = $comment->task->value();
110+
$url = url($task->url) . '#comment-' . $comment->position;
111+
112+
return [new ActivityEvent(
113+
date: $comment->publication_date,
114+
title: "Commented on \"{$task->title}\"",
115+
description: "Commented on <a href=\"{$url}\">{$task->title}</a>",
116+
longDescription: "<p>I just added the following comment on {$task->title}:</p>{$comment->content}",
117+
url: $url
118+
)];
119+
}
120+
121+
/**
122+
* @return array<ActivityEvent>
123+
*/
124+
private function createEventsFromPost(Entry $post): array
125+
{
126+
$url = url($post->url);
127+
128+
return [new ActivityEvent(
129+
date: $post->publication_date,
130+
title: "Published \"{$post->title}\"",
131+
description: "Published <a href=\"{$url}\">{$post->title}</a>",
132+
longDescription: "<p>I just published a new blog post: <a href=\"{$url}\">{$post->title}</a></p>",
133+
url: $url
134+
)];
135+
}
136+
}

app/Support/Facades/Activity.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\Support\Facades;
4+
5+
use App\Models\ActivityEvent;
6+
use Carbon\Carbon;
7+
use Illuminate\Support\Collection;
8+
use Illuminate\Support\Facades\Facade;
9+
10+
/**
11+
* @method static Collection<string, ActivityEvent> events();
12+
* @method static Carbon lastModificationDate();
13+
*
14+
* @see \App\Services\ActivityService
15+
*/
16+
class Activity extends Facade
17+
{
18+
protected static function getFacadeAccessor()
19+
{
20+
return 'activity';
21+
}
22+
}

content/collections/pages/beastmasters.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
---
22
id: beastmasters
3-
blueprint: page
43
title: 'BeastMasters: Twin Soul Legends'
5-
show_title: false
4+
blueprint: page
65
template: projects/show
76
---
87

content/collections/pages/blog.md

-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ id: blog
33
title: Blog
44
blueprint: page
55
icon: blog
6-
show_title: false
76
template: blog/index
87
---

content/collections/pages/geemba.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
---
22
id: geemba
3-
blueprint: page
43
title: Geemba
5-
show_title: false
4+
blueprint: page
65
template: projects/show
76
---
87

content/collections/pages/now.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
id: now
3+
title: Now
4+
blueprint: page
5+
icon: now
6+
template: now/index
7+
---
8+
9+
I am currently based in [Barcelona](https://en.wikipedia.org/wiki/Barcelona){:target="\_blank"}. I practice [Open Productivity](/blog/open-productivity), and here you can see what I'm up to these days:

content/collections/pages/tasks.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: tasks
3-
blueprint: page
43
title: Tasks
4+
blueprint: page
55
template: tasks/index
66
---
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
id: housekeeping-202425
3+
blueprint: task
4+
title: 'Housekeeping 2024/25'
5+
publication_date: '2024-12-27 09:12:10'
6+
---
7+
8+
Once in a while, I batch some chores I've been neglecting. And this liminal moment between years and [jobs](https://noeldemartin.com/blog/the-end-of-the-chapter) is the perfect time to do it.

content/trees/collections/pages.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ tree:
55
children:
66
- entry: beastmasters
77
- entry: geemba
8+
- entry: now
89
- entry: tasks

content/trees/navigation/main.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ tree:
55
entry: blog
66
- id: projects-nav
77
entry: projects
8+
- id: now-nav
9+
entry: now

phpstan.neon

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
includes:
22
- ./vendor/larastan/larastan/extension.neon
3-
43
parameters:
5-
4+
level: max
65
paths:
76
- app/
8-
9-
level: max
7+
excludePaths:
8+
- app/Services/ActivityService.php
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<svg
2+
class="{{ class ?? '' }}"
3+
xmlns="http://www.w3.org/2000/svg"
4+
viewBox="0 0 305.308 305.308"
5+
>
6+
<path
7+
d="M91.699,44.068c-5.181-4.469-11.91-7.185-19.286-7.185c-16.329,0-29.564,13.241-29.564,29.567 c0,4.427,1.002,8.616,2.747,12.386C58.378,64.341,74.042,52.441,91.699,44.068z"
8+
/>
9+
<path
10+
d="M152.656,305.308c72.993,0,132.169-59.176,132.169-132.167c0-65.368-47.463-119.611-109.808-130.245v-5.375v-5.383V18.124 h11.465c3.79,0,6.866-4.055,6.866-9.062c0-5.004-3.076-9.062-6.866-9.062h-67.658c-3.795,0-6.872,4.058-6.872,9.062 c0,5.007,3.076,9.062,6.872,9.062h11.459v14.009v5.383v5.375c-11.131,1.9-21.777,5.186-31.771,9.683 c-18.077,8.129-33.989,20.187-46.652,35.108c-19.555,23.042-31.379,52.858-31.379,85.454 C20.487,246.132,79.663,305.308,152.656,305.308z M74.79,95.899c7.438-7.498,15.943-13.93,25.277-19.045 c15.625-8.57,33.546-13.463,52.594-13.463c60.52,0,109.748,49.234,109.748,109.75c0,60.518-49.229,109.751-109.748,109.751 c-60.517,0-109.75-49.233-109.75-109.751C42.906,143.043,55.098,115.747,74.79,95.899z"
11+
/>
12+
<path
13+
d="M86.296,239.499c4.246,4.246,8.898,8.088,13.888,11.459c2.496,1.683,5.08,3.257,7.736,4.696 c13.297,7.224,28.536,11.33,44.731,11.33c2.025,0,4.034-0.067,6.025-0.197c9.963-0.626,19.506-2.812,28.387-6.317 c3.552-1.397,7-3.014,10.324-4.82c2.662-1.444,5.24-3.009,7.736-4.696c4.992-3.377,9.642-7.208,13.888-11.46 c16.984-16.983,27.485-40.44,27.485-66.357c0-25.914-10.501-49.378-27.485-66.36c-4.246-4.248-8.896-8.085-13.888-11.459 c-2.496-1.686-5.079-3.254-7.736-4.699c-3.324-1.807-6.772-3.412-10.324-4.818c-8.876-3.5-18.424-5.69-28.387-6.319 c-1.991-0.13-4-0.192-6.025-0.192c-16.2,0-31.434,4.104-44.731,11.325c-2.662,1.444-5.24,3.014-7.736,4.699 c-4.994,3.373-9.647,7.21-13.888,11.459c-16.984,16.981-27.488,40.441-27.488,66.36C58.813,199.058,69.317,222.515,86.296,239.499 z M152.656,98.462v70.309l-70.682,27.165C69.462,147.196,103.676,98.462,152.656,98.462z"
14+
/>
15+
</svg>

resources/views/now/index.blade.php

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
@extends('layout')
2+
3+
@php
4+
$events = \App\Support\Facades\Activity::events()->groupBy(fn ($event) => $event->date->year);
5+
$lastModificationDate = \App\Support\Facades\Activity::lastModificationDate();
6+
@endphp
7+
8+
@section('main')
9+
<div class="relative mb-4 flex flex-col">
10+
<h1 class="mb-0">What I'm doing now</h1>
11+
12+
<span class="text-blue-darker text-sm">
13+
Last updated
14+
<time datetime="{{ $lastModificationDate->toDateTimeString() }}">
15+
{{ $lastModificationDate->display('date') }}
16+
</time>
17+
</span>
18+
</div>
19+
20+
<div class="max-w-readable">
21+
@antlers
22+
{{ content }}
23+
@endantlers
24+
</div>
25+
26+
<s:collection
27+
from="tasks"
28+
completion_date:exists="false"
29+
sort="publication_date:desc"
30+
as="tasks"
31+
>
32+
@if ($tasks->isEmpty())
33+
<div
34+
class="bg-grey-lighter border-grey-light flex w-full flex-col items-center justify-center rounded-lg border p-8"
35+
>
36+
<s:partial src="icons/task-completed" class="size-20" />
37+
38+
<!-- prettier-ignore -->
39+
<p class="mt-8 mb-0 text-center text-lg">
40+
Seems like I have completed all my tasks! Come back later or check out my <a href="/tasks">previous tasks</a>.
41+
</p>
42+
</div>
43+
@else
44+
@foreach ($tasks as $task)
45+
<x-content-card :url="$task->url" :title="$task->title">
46+
@antlers
47+
{{ task:content }}
48+
@endantlers
49+
</x-content-card>
50+
@endforeach
51+
52+
<div class="mt-0 text-left md:-mt-8 md:text-right">
53+
<a href="/tasks">See previous tasks →</a>
54+
</div>
55+
56+
<hr class="border-grey mt-4 hidden w-full md:block" />
57+
@endif
58+
</s:collection>
59+
60+
<h2>Past activity</h2>
61+
62+
@foreach ($events as $year => $yearEvents)
63+
<details {{ $loop->first ? 'open' : '' }} class="mb-4">
64+
<summary
65+
class="hover:text-blue-darkest focus:text-blue-darkest mb-2 hover:cursor-pointer"
66+
>
67+
{{ $year }}
68+
</summary>
69+
70+
<ul class="ml-0 list-none p-0">
71+
@foreach ($yearEvents as $event)
72+
<x-comment tag="li" :date="$event->date" short="true">
73+
<p class="md:m-0">{!! $event->description !!}</p>
74+
</x-comment>
75+
@endforeach
76+
</ul>
77+
</details>
78+
@endforeach
79+
80+
<!-- prettier-ignore -->
81+
<p>
82+
Do you think this is cool? This is a <a href="https://nownownow.com/about" target="_blank">now page</a>,
83+
and you should have one too ;).
84+
</p>
85+
@endsection

0 commit comments

Comments
 (0)