Skip to content

Commit 101a8e6

Browse files
authored
Merge pull request #67 from thekid/refactor/reading-direction
Switch journeys to display entries oldest - newest
2 parents 50961a6 + b12a2a2 commit 101a8e6

File tree

11 files changed

+134
-72
lines changed

11 files changed

+134
-72
lines changed

src/main/handlebars/content.handlebars

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ parent: feed
5151
mapping.project(document.querySelector('#map'));
5252
5353
// Update statistics
54-
window.setTimeout(() => {
55-
fetch('/api/statistics/{{item.slug}}', {method: 'POST', body: '{{sign item.slug}}'});
56-
}, Math.min({{size item.images}} * 1500, 5000));
54+
{{&use 'statistics'}}
55+
const statistics = new Statistics();
56+
statistics.add('{{item.slug}}', '{{sign item.slug}}', Math.min({{size item.images}} * 1500, 5000));
57+
statistics.schedule('{{item.slug}}');
5758
</script>
5859
{{/inline}}
5960
{{/layout}}

src/main/handlebars/feed.handlebars

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@
2222
{{#with elements}}
2323
{{#each .}}
2424
<section>
25-
<h2 class="{{range-rel is.from is.until}} date"><a href="{{route this}}">{{title}}</a></h2>
25+
<h2>
26+
{{#with parent}}
27+
<span class="journey" title="Reise, {{range is.from is.until format="M Y"}}">
28+
<a href="{{route this}}">{{title}}</a>
29+
</span>
30+
{{/with}}
31+
<a href="{{route this}}">{{title}}</a>
32+
</h2>
2633

2734
<div class="meta">
2835
{{date ./date format="d.m.Y"}} @
@@ -34,11 +41,7 @@
3441
{{count views '' '(Eine Ansicht)' '(# Ansichten)'}}
3542
</div>
3643

37-
{{#if images}}
38-
{{> partials/images in=. first=@first}}
39-
{{else}}
40-
{{> partials/cards in=children}}
41-
{{/if}}
44+
{{> partials/images in=. first=@first}}
4245

4346
<div class="content">
4447
{{& content}}

src/main/handlebars/journey.handlebars

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ parent: feed
1414
{{/with}}
1515
{{/inline}}
1616
{{#*inline "main"}}
17-
<h1 class="title">Dialog - {{journey.title}}</h1>
17+
<h1 class="{{range-rel journey.is.from journey.is.until}} title">Dialog - {{journey.title}}</h1>
1818

1919
<!-- The journey itself -->
2020
<section id="summary">
@@ -63,6 +63,7 @@ parent: feed
6363
<li><a href="{{link}}">{{name}}</a></li>
6464
{{/each}}
6565
</ul>
66+
{{count views '' '(Eine Ansicht)' '(# Ansichten)'}}
6667
</div>
6768
{{> partials/images in=.}}
6869

@@ -92,10 +93,29 @@ parent: feed
9293
9394
mapping.project(document.querySelector('#map'));
9495
95-
// Update statistics
96-
window.setTimeout(() => {
97-
fetch('/api/statistics/{{journey.slug}}', {method: 'POST', body: '{{sign journey.slug}}'});
98-
}, 10000);
96+
// Update statistics for journey after a little while. To determine which entries the
97+
// user has spent time viewing, use intersection observer
98+
{{&use 'statistics'}}
99+
const statistics = new Statistics();
100+
statistics.add('{{journey.slug}}', '{{sign journey.slug}}', 10000);
101+
statistics.schedule('{{journey.slug}}');
102+
103+
const observer = new IntersectionObserver(
104+
(entries) => {
105+
for (const entry of entries) {
106+
if (entry.isIntersecting) {
107+
statistics.schedule('{{journey.slug}}/' + entry.target.id);
108+
} else {
109+
statistics.withdraw('{{journey.slug}}/' + entry.target.id);
110+
}
111+
}
112+
},
113+
{ threshold: 0.2 }
114+
);
115+
{{#each itinerary}}
116+
observer.observe(document.querySelector('#{{scroll slug}}'));
117+
statistics.add('{{slug}}', '{{sign slug}}', Math.min({{size images}} * 1500, 5000));
118+
{{/each}}
99119
</script>
100120
{{/inline}}
101121
{{/layout}}

src/main/handlebars/journeys.handlebars

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
<div class="cards">
2020
{{#each journeys}}
2121
<div class="card">
22-
<div class="{{range-rel is.from is.until}} date">{{range is.from is.until format="M Y"}}</div>
23-
<a title="{{title}}" href="/journey/{{slug}}">
22+
<div class="{{range-rel is.from is.until}} context">{{range is.from is.until format="M Y"}}</div>
23+
<a title="{{title}}" href="{{route this}}">
2424
{{#with preview}}<img alt="{{title}}, {{date meta.dateTime format='d.m.Y H:i'}}" {{#unless (top 3 @index)}}loading="lazy"{{/unless}} src="/image/{{slug}}/thumb-{{name}}.webp">{{else}}<div class="without-preview"></div>{{/with}}
2525
<h3>{{title}}</h3>
2626
</a>

src/main/handlebars/layout.handlebars

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -360,25 +360,35 @@
360360
box-shadow: .5rem .5rem 1rem rgb(0 0 0 / .2);
361361
}
362362
363-
.date {
363+
.context {
364364
position: absolute;
365365
top: .75rem;
366366
left: .75rem;
367-
background-color: black;
367+
background-color: var(--main-color);
368368
border-radius: .25rem;
369-
color: white;
369+
color: var(--text-color);
370370
padding: .125rem .4rem;
371-
}
372371
373-
.current.date {
374-
background-color: orange;
375-
}
372+
.journey a {
373+
color: var(--text-color);
374+
}
376375
377-
.current.date::before {
378-
content: 'LIVE';
379-
margin-right: .25rem;
380-
padding-right: .25rem;
381-
border-right: 1px solid white;
376+
.journey::before {
377+
content: '';
378+
margin-right: .25rem;
379+
}
380+
381+
&.current {
382+
background-color: orange;
383+
color: white;
384+
}
385+
386+
&.current::before {
387+
content: 'LIVE';
388+
margin-right: .25rem;
389+
padding-right: .25rem;
390+
border-right: 1px solid white;
391+
}
382392
}
383393
384394
h3 {
@@ -395,6 +405,15 @@
395405
}
396406
}
397407
408+
h1.current::before {
409+
content: 'LIVE';
410+
background-color: orange;
411+
margin-right: .5rem;
412+
padding: .125rem .4rem;
413+
border-radius: .25rem;
414+
font-size: 1.15rem;
415+
}
416+
398417
section {
399418
padding-top: 1rem;
400419
margin-bottom: 3rem;
@@ -403,6 +422,10 @@
403422
h2 {
404423
font-size: 1.5rem;
405424
font-weight: 700;
425+
426+
.journey::after {
427+
content: '»';
428+
}
406429
}
407430
408431
h2.scroll {
@@ -414,15 +437,6 @@
414437
color: var(--text-color);
415438
}
416439
417-
h2.current::before {
418-
content: 'LIVE';
419-
background-color: orange;
420-
margin-right: .5rem;
421-
padding: .125rem .4rem;
422-
border-radius: .25rem;
423-
font-size: 1rem;
424-
}
425-
426440
h2 .top {
427441
text-decoration: none;
428442
color: orange;

src/main/handlebars/partials/cards.handlebars

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010
{{else}}
1111
<div class="card">
1212
{{#if is.content}}
13-
<div class="date">{{date ./date format='d.m.Y'}}</div>
13+
<div class="context">
14+
<span class="date">{{date ./date format='d.m.Y'}}</span>
15+
{{#with parent}}
16+
<span class="journey"><a href="{{route this}}">{{title}}</a></span>
17+
{{/with}}
18+
</div>
1419
{{else if is.journey}}
15-
<div class="{{range-rel is.from is.until}} date">{{range is.from is.until format="M Y"}}</div>
20+
<div class="{{range-rel is.from is.until}} context">{{range is.from is.until format="M Y"}}</div>
1621
{{/if}}
1722
<a title="{{title}}" href="{{route this}}">
1823
{{#with preview}}<img alt="{{title}}, {{date meta.dateTime format='d.m.Y H:i'}}" src="/image/{{slug}}/thumb-{{name}}.webp">{{else}}<div class="without-preview"></div>{{/with}}

src/main/js/statistics.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class Statistics {
2+
#tasks = {};
3+
4+
/** Adds a statistics target with a given delay */
5+
add(target, signature, delay) {
6+
this.#tasks[target] = { signature, delay, timer : null, completed : false };
7+
}
8+
9+
/** Schedules the given statistics target */
10+
schedule(target) {
11+
const task = this.#tasks[target];
12+
if (task === undefined) throw new Error(`Undefined target ${target}`);
13+
14+
task.timer && clearTimeout(task.timer);
15+
task.completed || (task.timer = setTimeout(
16+
() => fetch('/api/statistics/' + target, { method: 'POST', body: task.signature }).then(() => task.completed = true),
17+
task.delay
18+
));
19+
}
20+
21+
/** Withdraws any scheduling for the given statistics target */
22+
withdraw(target) {
23+
const task = this.#tasks[target];
24+
if (task === undefined) return;
25+
26+
clearTimeout(task.timer);
27+
task.timer = null;
28+
}
29+
}

src/main/php/de/thekid/dialog/Repository.php

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66
use util\{Date, Secret};
77

88
class Repository {
9+
private const WITH_PARENT= [
10+
['$lookup' => [
11+
'from' => 'entries',
12+
'localField' => 'parent',
13+
'foreignField' => 'slug',
14+
'as' => 'parent',
15+
]],
16+
['$addFields' => ['parent' => ['$first' => '$parent']]],
17+
];
918
private $passwords= Hashing::sha256();
1019

1120
public function __construct(private Database $database) { }
@@ -19,13 +28,14 @@ public function authenticate(string $user, Secret $secret): ?Document {
1928
return $cursor->first();
2029
}
2130

22-
/** Returns newest (top-level) entries */
31+
/** Returns newest entries */
2332
public function newest(int $limit): array<Document> {
2433
$cursor= $this->database->collection('entries')->aggregate([
25-
['$match' => ['parent' => ['$eq' => null], 'published' => ['$lt' => Date::now()]]],
34+
['$match' => ['is.journey' => ['$ne' => true], 'published' => ['$lt' => Date::now()]]],
2635
['$unset' => '_searchable'],
2736
['$sort' => ['date' => -1]],
2837
['$limit' => $limit],
38+
...self::WITH_PARENT,
2939
]);
3040
return $cursor->all();
3141
}
@@ -40,36 +50,16 @@ public function journeys(): array<Document> {
4050
return $cursor->all();
4151
}
4252

43-
/** Returns paginated (top-level) entries */
44-
public function entries(Pagination $pagination, int $page, int $children= 6): array<Document> {
45-
$entries= $this->database->collection('entries');
46-
$cursor= $entries->aggregate([
47-
['$match' => ['parent' => ['$eq' => null], 'published' => ['$lt' => Date::now()]]],
53+
/** Returns paginated entries */
54+
public function entries(Pagination $pagination, int $page): array<Document> {
55+
$cursor= $this->database->collection('entries')->aggregate([
56+
['$match' => ['is.journey' => ['$ne' => true], 'published' => ['$lt' => Date::now()]]],
4857
['$unset' => '_searchable'],
4958
['$sort' => ['date' => -1]],
5059
['$skip' => $pagination->skip($page)],
5160
['$limit' => $pagination->limit()],
52-
53-
// If no preview images are set, aggregate children
54-
['$lookup' => [
55-
'from' => 'entries',
56-
'let' => ['parent' => '$slug', 'images' => ['$size' => ['$ifNull' => ['$images', []]]]],
57-
'pipeline' => [
58-
['$match' => ['$expr' => ['$cond' => [
59-
['$eq' => ['$$images', 0]],
60-
['$eq' => ['$parent', '$$parent']],
61-
['$eq' => ['$_id', null]],
62-
]]]]
63-
],
64-
'as' => 'children',
65-
]],
66-
['$addFields' => ['children' => ['$map' => [
67-
'input' => ['$sortArray' => ['input' => '$children', 'sortBy' => ['date' => -1]]],
68-
'as' => 'it',
69-
'in' => ['$unsetField' => ['input' => '$$it', 'field' => '_searchable']],
70-
]]]],
61+
...self::WITH_PARENT,
7162
]);
72-
7363
return $pagination->paginate($page, $cursor);
7464
}
7565

@@ -145,11 +135,11 @@ public function entry(string $slug, bool $published= true): ?Document {
145135
}
146136

147137
/** Returns an entry's children, latest first */
148-
public function children(string $slug): Cursor {
138+
public function children(string $slug, array<string, mixed> $sort= ['date' => -1]): Cursor {
149139
return $this->database->collection('entries')->aggregate([
150-
['$match' => ['parent' => ['$eq' => $slug], 'published' => ['$lt' => Date::now()]]],
140+
['$match' => ['parent' => $slug, 'published' => ['$lt' => Date::now()]]],
151141
['$unset' => '_searchable'],
152-
['$sort' => ['date' => -1]],
142+
['$sort' => $sort],
153143
]);
154144
}
155145

src/main/php/de/thekid/dialog/web/Feed.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#[Handler('/feed')]
77
class Feed {
8-
private $pagination= new Pagination(5);
8+
private $pagination= new Pagination(8);
99

1010
public function __construct(private Repository $repository) { }
1111

src/main/php/de/thekid/dialog/web/Home.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public function __construct(private Repository $repository) { }
1212
public function index() {
1313
return View::named('home')->with([
1414
'cover' => $this->repository->entry('@cover'),
15-
'newest' => $this->repository->newest(6),
15+
'newest' => $this->repository->newest(9),
1616
]);
1717
}
1818

0 commit comments

Comments
 (0)