@rmdes/indiekit-endpoint-microsub 1.0.38 → 1.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/styles.css CHANGED
@@ -1015,6 +1015,49 @@
1015
1015
  color: #7c3aed;
1016
1016
  }
1017
1017
 
1018
+ /* ==========================================================================
1019
+ Breadcrumbs
1020
+ ========================================================================== */
1021
+
1022
+ .breadcrumbs {
1023
+ margin-bottom: var(--space-xs);
1024
+ }
1025
+
1026
+ .breadcrumbs__list {
1027
+ align-items: center;
1028
+ display: flex;
1029
+ flex-wrap: wrap;
1030
+ font-size: var(--font-size-small);
1031
+ gap: 0;
1032
+ list-style: none;
1033
+ margin: 0;
1034
+ padding: 0;
1035
+ }
1036
+
1037
+ .breadcrumbs__item::before {
1038
+ color: var(--color-text-muted);
1039
+ content: "/";
1040
+ margin: 0 var(--space-xs);
1041
+ }
1042
+
1043
+ .breadcrumbs__item:first-child::before {
1044
+ content: none;
1045
+ margin: 0;
1046
+ }
1047
+
1048
+ .breadcrumbs__link {
1049
+ color: var(--color-primary);
1050
+ text-decoration: none;
1051
+ }
1052
+
1053
+ .breadcrumbs__link:hover {
1054
+ text-decoration: underline;
1055
+ }
1056
+
1057
+ .breadcrumbs__current {
1058
+ color: var(--color-text-muted);
1059
+ }
1060
+
1018
1061
  /* ==========================================================================
1019
1062
  View Switcher
1020
1063
  ========================================================================== */
@@ -1072,19 +1115,20 @@
1072
1115
  }
1073
1116
 
1074
1117
  .timeline-view__item {
1075
- border-radius: var(--border-radius);
1076
1118
  position: relative;
1077
1119
  }
1078
1120
 
1079
- .timeline-view__item .item-card {
1080
- border-left: none;
1081
- }
1082
-
1083
- .timeline-view__channel-label {
1084
- display: block;
1085
- font-size: 0.75rem;
1121
+ .timeline-view__channel-badge {
1122
+ border-radius: 3px;
1123
+ color: #fff;
1124
+ display: inline-block;
1125
+ font-size: 0.6875rem;
1086
1126
  font-weight: 600;
1087
- padding: 0 var(--space-s) var(--space-xs);
1127
+ letter-spacing: 0.02em;
1128
+ line-height: 1;
1129
+ margin-bottom: var(--space-xs);
1130
+ padding: 3px 8px;
1131
+ text-transform: uppercase;
1088
1132
  }
1089
1133
 
1090
1134
  .timeline-view__filter {
@@ -46,9 +46,9 @@ import { getDeckConfig, saveDeckConfig } from "../storage/deck.js";
46
46
  * @param {object} response - Express response
47
47
  */
48
48
  export async function index(request, response) {
49
- const lastView = request.session?.microsubView || "channels";
49
+ const lastView = request.session?.microsubView || "timeline";
50
50
  const validViews = ["channels", "deck", "timeline"];
51
- const view = validViews.includes(lastView) ? lastView : "channels";
51
+ const view = validViews.includes(lastView) ? lastView : "timeline";
52
52
  response.redirect(`${request.baseUrl}/${view}`);
53
53
  }
54
54
 
@@ -71,6 +71,10 @@ export async function channels(request, response) {
71
71
  baseUrl: request.baseUrl,
72
72
  readerBaseUrl: request.baseUrl,
73
73
  activeView: "channels",
74
+ breadcrumbs: [
75
+ { text: "Reader", href: request.baseUrl },
76
+ { text: "Channels" },
77
+ ],
74
78
  });
75
79
  }
76
80
 
@@ -85,6 +89,11 @@ export async function newChannel(request, response) {
85
89
  baseUrl: request.baseUrl,
86
90
  readerBaseUrl: request.baseUrl,
87
91
  activeView: "channels",
92
+ breadcrumbs: [
93
+ { text: "Reader", href: request.baseUrl },
94
+ { text: "Channels", href: `${request.baseUrl}/channels` },
95
+ { text: request.__("microsub.channels.new") },
96
+ ],
88
97
  });
89
98
  }
90
99
 
@@ -157,6 +166,11 @@ export async function channel(request, response) {
157
166
  baseUrl: request.baseUrl,
158
167
  readerBaseUrl: request.baseUrl,
159
168
  activeView: "channels",
169
+ breadcrumbs: [
170
+ { text: "Reader", href: request.baseUrl },
171
+ { text: "Channels", href: `${request.baseUrl}/channels` },
172
+ { text: channelDocument.name },
173
+ ],
160
174
  });
161
175
  }
162
176
 
@@ -184,6 +198,12 @@ export async function settings(request, response) {
184
198
  baseUrl: request.baseUrl,
185
199
  readerBaseUrl: request.baseUrl,
186
200
  activeView: "channels",
201
+ breadcrumbs: [
202
+ { text: "Reader", href: request.baseUrl },
203
+ { text: "Channels", href: `${request.baseUrl}/channels` },
204
+ { text: channelDocument.name, href: `${request.baseUrl}/channels/${uid}` },
205
+ { text: "Settings" },
206
+ ],
187
207
  });
188
208
  }
189
209
 
@@ -273,6 +293,12 @@ export async function feeds(request, response) {
273
293
  baseUrl: request.baseUrl,
274
294
  readerBaseUrl: request.baseUrl,
275
295
  activeView: "channels",
296
+ breadcrumbs: [
297
+ { text: "Reader", href: request.baseUrl },
298
+ { text: "Channels", href: `${request.baseUrl}/channels` },
299
+ { text: channelDocument.name, href: `${request.baseUrl}/channels/${uid}` },
300
+ { text: "Feeds" },
301
+ ],
276
302
  });
277
303
  }
278
304
 
@@ -354,6 +380,17 @@ export async function item(request, response) {
354
380
  channel = await channelsCollection.findOne({ _id: itemDocument.channelId });
355
381
  }
356
382
 
383
+ const itemBreadcrumbs = [
384
+ { text: "Reader", href: request.baseUrl },
385
+ ];
386
+ if (channel) {
387
+ itemBreadcrumbs.push(
388
+ { text: "Channels", href: `${request.baseUrl}/channels` },
389
+ { text: channel.name, href: `${request.baseUrl}/channels/${channel.uid}` },
390
+ );
391
+ }
392
+ itemBreadcrumbs.push({ text: itemDocument.name || "Item" });
393
+
357
394
  response.render("item", {
358
395
  title: itemDocument.name || "Item",
359
396
  item: itemDocument,
@@ -361,6 +398,7 @@ export async function item(request, response) {
361
398
  baseUrl: request.baseUrl,
362
399
  readerBaseUrl: request.baseUrl,
363
400
  activeView: "channels",
401
+ breadcrumbs: itemBreadcrumbs,
364
402
  });
365
403
  }
366
404
 
@@ -473,6 +511,10 @@ export async function compose(request, response) {
473
511
  baseUrl: request.baseUrl,
474
512
  readerBaseUrl: request.baseUrl,
475
513
  activeView: "channels",
514
+ breadcrumbs: [
515
+ { text: "Reader", href: request.baseUrl },
516
+ { text: "Compose" },
517
+ ],
476
518
  });
477
519
  }
478
520
 
@@ -648,6 +690,10 @@ export async function searchPage(request, response) {
648
690
  baseUrl: request.baseUrl,
649
691
  readerBaseUrl: request.baseUrl,
650
692
  activeView: "channels",
693
+ breadcrumbs: [
694
+ { text: "Reader", href: request.baseUrl },
695
+ { text: "Search" },
696
+ ],
651
697
  });
652
698
  }
653
699
 
@@ -686,6 +732,10 @@ export async function searchFeeds(request, response) {
686
732
  baseUrl: request.baseUrl,
687
733
  readerBaseUrl: request.baseUrl,
688
734
  activeView: "channels",
735
+ breadcrumbs: [
736
+ { text: "Reader", href: request.baseUrl },
737
+ { text: "Search" },
738
+ ],
689
739
  });
690
740
  }
691
741
 
@@ -719,6 +769,10 @@ export async function subscribe(request, response) {
719
769
  baseUrl: request.baseUrl,
720
770
  readerBaseUrl: request.baseUrl,
721
771
  activeView: "channels",
772
+ breadcrumbs: [
773
+ { text: "Reader", href: request.baseUrl },
774
+ { text: "Search" },
775
+ ],
722
776
  });
723
777
  }
724
778
 
@@ -811,6 +865,13 @@ export async function editFeedForm(request, response) {
811
865
  baseUrl: request.baseUrl,
812
866
  readerBaseUrl: request.baseUrl,
813
867
  activeView: "channels",
868
+ breadcrumbs: [
869
+ { text: "Reader", href: request.baseUrl },
870
+ { text: "Channels", href: `${request.baseUrl}/channels` },
871
+ { text: channelDocument.name, href: `${request.baseUrl}/channels/${uid}` },
872
+ { text: "Feeds", href: `${request.baseUrl}/channels/${uid}/feeds` },
873
+ { text: "Edit" },
874
+ ],
814
875
  });
815
876
  }
816
877
 
@@ -848,6 +909,13 @@ export async function updateFeedUrl(request, response) {
848
909
  baseUrl: request.baseUrl,
849
910
  readerBaseUrl: request.baseUrl,
850
911
  activeView: "channels",
912
+ breadcrumbs: [
913
+ { text: "Reader", href: request.baseUrl },
914
+ { text: "Channels", href: `${request.baseUrl}/channels` },
915
+ { text: channelDocument.name, href: `${request.baseUrl}/channels/${uid}` },
916
+ { text: "Feeds", href: `${request.baseUrl}/channels/${uid}/feeds` },
917
+ { text: "Edit" },
918
+ ],
851
919
  });
852
920
  }
853
921
 
@@ -1029,6 +1097,10 @@ export async function actorProfile(request, response) {
1029
1097
  baseUrl: request.baseUrl,
1030
1098
  readerBaseUrl: request.baseUrl,
1031
1099
  activeView: "channels",
1100
+ breadcrumbs: [
1101
+ { text: "Reader", href: request.baseUrl },
1102
+ { text: actor.name || "Actor" },
1103
+ ],
1032
1104
  });
1033
1105
  } catch (error) {
1034
1106
  console.error(`[Microsub] Actor profile fetch failed: ${error.message}`);
@@ -1043,6 +1115,10 @@ export async function actorProfile(request, response) {
1043
1115
  readerBaseUrl: request.baseUrl,
1044
1116
  activeView: "channels",
1045
1117
  error: "Could not fetch this actor's profile. They may have restricted access.",
1118
+ breadcrumbs: [
1119
+ { text: "Reader", href: request.baseUrl },
1120
+ { text: "Actor" },
1121
+ ],
1046
1122
  });
1047
1123
  }
1048
1124
  }
@@ -1163,6 +1239,10 @@ export async function timeline(request, response) {
1163
1239
  baseUrl: request.baseUrl,
1164
1240
  readerBaseUrl: request.baseUrl,
1165
1241
  activeView: "timeline",
1242
+ breadcrumbs: [
1243
+ { text: "Reader", href: request.baseUrl },
1244
+ { text: "Timeline" },
1245
+ ],
1166
1246
  });
1167
1247
  }
1168
1248
 
@@ -1223,6 +1303,10 @@ export async function deck(request, response) {
1223
1303
  baseUrl: request.baseUrl,
1224
1304
  readerBaseUrl: request.baseUrl,
1225
1305
  activeView: "deck",
1306
+ breadcrumbs: [
1307
+ { text: "Reader", href: request.baseUrl },
1308
+ { text: "Deck" },
1309
+ ],
1226
1310
  });
1227
1311
  }
1228
1312
 
@@ -1249,6 +1333,11 @@ export async function deckSettings(request, response) {
1249
1333
  baseUrl: request.baseUrl,
1250
1334
  readerBaseUrl: request.baseUrl,
1251
1335
  activeView: "deck",
1336
+ breadcrumbs: [
1337
+ { text: "Reader", href: request.baseUrl },
1338
+ { text: "Deck", href: `${request.baseUrl}/deck` },
1339
+ { text: "Settings" },
1340
+ ],
1252
1341
  });
1253
1342
  }
1254
1343
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-microsub",
3
- "version": "1.0.38",
3
+ "version": "1.0.39",
4
4
  "description": "Microsub endpoint for Indiekit. Enables subscribing to feeds and reading content using the Microsub protocol.",
5
5
  "keywords": [
6
6
  "indiekit",
package/views/channel.njk CHANGED
@@ -3,9 +3,7 @@
3
3
  {% block reader %}
4
4
  <div class="channel">
5
5
  <header class="channel__header">
6
- <a href="{{ baseUrl }}/channels" class="back-link">
7
- {{ icon("previous") }} {{ __("microsub.channels.title") }}
8
- </a>
6
+ <h1>{{ channel.name }}</h1>
9
7
  <div class="channel__actions">
10
8
  {% if not showRead and items.length > 0 %}
11
9
  <form action="{{ baseUrl }}/api/mark-read" method="POST" style="display: inline;">
@@ -6,6 +6,7 @@
6
6
 
7
7
  {% block content %}
8
8
  <link rel="stylesheet" href="/assets/@rmdes-indiekit-endpoint-microsub/styles.css">
9
+ {% include "partials/breadcrumbs.njk" %}
9
10
  {% include "partials/view-switcher.njk" %}
10
11
  {% block reader %}{% endblock %}
11
12
  {% endblock %}
@@ -0,0 +1,16 @@
1
+ {# Breadcrumb navigation #}
2
+ {% if breadcrumbs and breadcrumbs.length > 0 %}
3
+ <nav class="breadcrumbs" aria-label="Breadcrumb">
4
+ <ol class="breadcrumbs__list">
5
+ {% for crumb in breadcrumbs %}
6
+ <li class="breadcrumbs__item">
7
+ {% if crumb.href %}
8
+ <a href="{{ crumb.href }}" class="breadcrumbs__link">{{ crumb.text }}</a>
9
+ {% else %}
10
+ <span class="breadcrumbs__current" aria-current="page">{{ crumb.text }}</span>
11
+ {% endif %}
12
+ </li>
13
+ {% endfor %}
14
+ </ol>
15
+ </nav>
16
+ {% endif %}
@@ -31,13 +31,13 @@
31
31
  {% if items.length > 0 %}
32
32
  <div class="timeline" id="timeline">
33
33
  {% for item in items %}
34
- <div class="timeline-view__item" style="border-left: 4px solid {{ item._channelColor or '#ccc' }}">
35
- {% include "partials/item-card.njk" %}
34
+ <div class="timeline-view__item">
36
35
  {% if item._channelName %}
37
- <span class="timeline-view__channel-label" style="color: {{ item._channelColor or '#888' }}">
36
+ <span class="timeline-view__channel-badge" style="background: {{ item._channelColor or '#888' }}">
38
37
  {{ item._channelName }}
39
38
  </span>
40
39
  {% endif %}
40
+ {% include "partials/item-card.njk" %}
41
41
  </div>
42
42
  {% endfor %}
43
43
  </div>