@rmdes/indiekit-endpoint-microsub 1.0.1 → 1.0.3

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/views/item.njk CHANGED
@@ -1,20 +1,26 @@
1
- {% extends "document.njk" %}
1
+ {% extends "layouts/reader.njk" %}
2
2
 
3
- {% block content %}
3
+ {% block reader %}
4
4
  <article class="item">
5
- <a href="{{ baseUrl }}/channels" class="back-link">
5
+ <a href="{{ backUrl or (baseUrl + '/channels') }}" class="back-link">
6
6
  {{ icon("previous") }} {{ __("Back") }}
7
7
  </a>
8
8
 
9
9
  {% if item.author %}
10
10
  <header class="item__author">
11
11
  {% if item.author.photo %}
12
- <img src="{{ item.author.photo }}" alt="" class="item__author-photo" width="48" height="48" loading="lazy">
12
+ <img src="{{ item.author.photo }}"
13
+ alt=""
14
+ class="item__author-photo"
15
+ width="48"
16
+ height="48"
17
+ loading="lazy"
18
+ onerror="this.style.display='none'">
13
19
  {% endif %}
14
20
  <div class="item__author-info">
15
21
  <span class="item__author-name">
16
22
  {% if item.author.url %}
17
- <a href="{{ item.author.url }}">{{ item.author.name or item.author.url }}</a>
23
+ <a href="{{ item.author.url }}" target="_blank" rel="noopener">{{ item.author.name or item.author.url }}</a>
18
24
  {% else %}
19
25
  {{ item.author.name or "Unknown" }}
20
26
  {% endif %}
@@ -28,6 +34,44 @@
28
34
  </header>
29
35
  {% endif %}
30
36
 
37
+ {# Context for interactions #}
38
+ {% if item["in-reply-to"] or item["like-of"] or item["repost-of"] or item["bookmark-of"] %}
39
+ <div class="item__context">
40
+ {% if item["in-reply-to"] and item["in-reply-to"].length > 0 %}
41
+ <p class="item__context-label">
42
+ {{ icon("reply") }} {{ __("Reply to") }}:
43
+ <a href="{{ item['in-reply-to'][0] }}" target="_blank" rel="noopener">
44
+ {{ item["in-reply-to"][0] | replace("https://", "") | replace("http://", "") }}
45
+ </a>
46
+ </p>
47
+ {% endif %}
48
+ {% if item["like-of"] and item["like-of"].length > 0 %}
49
+ <p class="item__context-label">
50
+ {{ icon("like") }} {{ __("Liked") }}:
51
+ <a href="{{ item['like-of'][0] }}" target="_blank" rel="noopener">
52
+ {{ item["like-of"][0] | replace("https://", "") | replace("http://", "") }}
53
+ </a>
54
+ </p>
55
+ {% endif %}
56
+ {% if item["repost-of"] and item["repost-of"].length > 0 %}
57
+ <p class="item__context-label">
58
+ {{ icon("repost") }} {{ __("Reposted") }}:
59
+ <a href="{{ item['repost-of'][0] }}" target="_blank" rel="noopener">
60
+ {{ item["repost-of"][0] | replace("https://", "") | replace("http://", "") }}
61
+ </a>
62
+ </p>
63
+ {% endif %}
64
+ {% if item["bookmark-of"] and item["bookmark-of"].length > 0 %}
65
+ <p class="item__context-label">
66
+ {{ icon("bookmark") }} {{ __("Bookmarked") }}:
67
+ <a href="{{ item['bookmark-of'][0] }}" target="_blank" rel="noopener">
68
+ {{ item["bookmark-of"][0] | replace("https://", "") | replace("http://", "") }}
69
+ </a>
70
+ </p>
71
+ {% endif %}
72
+ </div>
73
+ {% endif %}
74
+
31
75
  {% if item.name %}
32
76
  <h2 class="item__title">{{ item.name }}</h2>
33
77
  {% endif %}
@@ -42,43 +86,65 @@
42
86
  </div>
43
87
  {% endif %}
44
88
 
89
+ {# Categories #}
90
+ {% if item.category and item.category.length > 0 %}
91
+ <div class="item-card__categories">
92
+ {% for cat in item.category %}
93
+ <span class="item-card__category">#{{ cat | replace("#", "") }}</span>
94
+ {% endfor %}
95
+ </div>
96
+ {% endif %}
97
+
98
+ {# Photos #}
45
99
  {% if item.photo and item.photo.length > 0 %}
46
100
  <div class="item__photos">
47
101
  {% for photo in item.photo %}
48
- <img src="{{ photo }}" alt="" class="item__photo" loading="lazy">
102
+ <a href="{{ photo }}" target="_blank" rel="noopener">
103
+ <img src="{{ photo }}" alt="" class="item__photo" loading="lazy">
104
+ </a>
49
105
  {% endfor %}
50
106
  </div>
51
107
  {% endif %}
52
108
 
53
- {% if item.inReplyTo or item.likeOf or item.repostOf or item.bookmarkOf %}
54
- <div class="item__context">
55
- {% if item.inReplyTo %}
56
- <p>{{ icon("reply") }} {{ __("Reply to") }}: <a href="{{ item.inReplyTo[0] }}">{{ item.inReplyTo[0] }}</a></p>
57
- {% endif %}
58
- {% if item.likeOf %}
59
- <p>{{ icon("like") }} {{ __("Liked") }}: <a href="{{ item.likeOf[0] }}">{{ item.likeOf[0] }}</a></p>
60
- {% endif %}
61
- {% if item.repostOf %}
62
- <p>{{ icon("repost") }} {{ __("Reposted") }}: <a href="{{ item.repostOf[0] }}">{{ item.repostOf[0] }}</a></p>
63
- {% endif %}
64
- {% if item.bookmarkOf %}
65
- <p>{{ icon("bookmark") }} {{ __("Bookmarked") }}: <a href="{{ item.bookmarkOf[0] }}">{{ item.bookmarkOf[0] }}</a></p>
66
- {% endif %}
109
+ {# Video #}
110
+ {% if item.video and item.video.length > 0 %}
111
+ <div class="item__media">
112
+ {% for video in item.video %}
113
+ <video src="{{ video }}"
114
+ controls
115
+ preload="metadata"
116
+ {% if item.photo and item.photo.length > 0 %}poster="{{ item.photo[0] }}"{% endif %}>
117
+ </video>
118
+ {% endfor %}
119
+ </div>
120
+ {% endif %}
121
+
122
+ {# Audio #}
123
+ {% if item.audio and item.audio.length > 0 %}
124
+ <div class="item__media">
125
+ {% for audio in item.audio %}
126
+ <audio src="{{ audio }}" controls preload="metadata"></audio>
127
+ {% endfor %}
67
128
  </div>
68
129
  {% endif %}
69
130
 
70
131
  <footer class="item__actions">
71
- <a href="{{ baseUrl }}/compose?replyTo={{ item.url | urlencode }}" class="button button--secondary button--small">
132
+ {% if item.url %}
133
+ <a href="{{ item.url }}" class="button button--secondary button--small" target="_blank" rel="noopener">
134
+ {{ icon("external") }} {{ __("microsub.item.viewOriginal") }}
135
+ </a>
136
+ {% endif %}
137
+ <a href="{{ baseUrl }}/compose?reply={{ item.url | urlencode }}" class="button button--secondary button--small">
72
138
  {{ icon("reply") }} {{ __("microsub.item.reply") }}
73
139
  </a>
74
- <a href="{{ baseUrl }}/compose?likeOf={{ item.url | urlencode }}" class="button button--secondary button--small">
140
+ <a href="{{ baseUrl }}/compose?like={{ item.url | urlencode }}" class="button button--secondary button--small">
75
141
  {{ icon("like") }} {{ __("microsub.item.like") }}
76
142
  </a>
77
- <a href="{{ baseUrl }}/compose?repostOf={{ item.url | urlencode }}" class="button button--secondary button--small">
143
+ <a href="{{ baseUrl }}/compose?repost={{ item.url | urlencode }}" class="button button--secondary button--small">
78
144
  {{ icon("repost") }} {{ __("microsub.item.repost") }}
79
145
  </a>
80
- <a href="{{ item.url }}" class="button button--secondary button--small" target="_blank" rel="noopener">
81
- {{ icon("public") }} {{ __("microsub.item.viewOriginal") }}
146
+ <a href="{{ baseUrl }}/compose?bookmark={{ item.url | urlencode }}" class="button button--secondary button--small">
147
+ {{ icon("bookmark") }} {{ __("microsub.item.bookmark") }}
82
148
  </a>
83
149
  </footer>
84
150
  </article>
@@ -0,0 +1,10 @@
1
+ {#
2
+ Microsub Reader Layout
3
+ Extends document.njk and adds reader-specific stylesheet
4
+ #}
5
+ {% extends "document.njk" %}
6
+
7
+ {% block content %}
8
+ <link rel="stylesheet" href="/assets/@rmdes-indiekit-endpoint-microsub/styles.css">
9
+ {% block reader %}{% endblock %}
10
+ {% endblock %}
@@ -1,56 +1,130 @@
1
- {# Item card for timeline display #}
2
- <article class="item-card{% if item._is_read %} item-card--read{% endif %}">
3
- <a href="{{ baseUrl }}/item/{{ item.uid }}" class="item-card__link">
1
+ {#
2
+ Item card for timeline display
3
+ Inspired by Aperture/Monocle reader
4
+ #}
5
+ <article class="item-card{% if item._is_read %} item-card--read{% endif %}"
6
+ data-item-id="{{ item._id }}"
7
+ data-is-read="{{ item._is_read | default(false) }}">
8
+
9
+ {# Context bar for interactions (Aperture pattern) #}
10
+ {% if item["like-of"] and item["like-of"].length > 0 %}
11
+ <div class="item-card__context">
12
+ {{ icon("like") }}
13
+ <span>Liked</span>
14
+ <a href="{{ item['like-of'][0] }}" target="_blank" rel="noopener">
15
+ {{ item['like-of'][0] | replace("https://", "") | replace("http://", "") | truncate(50) }}
16
+ </a>
17
+ </div>
18
+ {% elif item["repost-of"] and item["repost-of"].length > 0 %}
19
+ <div class="item-card__context">
20
+ {{ icon("repost") }}
21
+ <span>Reposted</span>
22
+ <a href="{{ item['repost-of'][0] }}" target="_blank" rel="noopener">
23
+ {{ item['repost-of'][0] | replace("https://", "") | replace("http://", "") | truncate(50) }}
24
+ </a>
25
+ </div>
26
+ {% elif item["in-reply-to"] and item["in-reply-to"].length > 0 %}
27
+ <div class="item-card__context">
28
+ {{ icon("reply") }}
29
+ <span>Reply to</span>
30
+ <a href="{{ item['in-reply-to'][0] }}" target="_blank" rel="noopener">
31
+ {{ item['in-reply-to'][0] | replace("https://", "") | replace("http://", "") | truncate(50) }}
32
+ </a>
33
+ </div>
34
+ {% elif item["bookmark-of"] and item["bookmark-of"].length > 0 %}
35
+ <div class="item-card__context">
36
+ {{ icon("bookmark") }}
37
+ <span>Bookmarked</span>
38
+ <a href="{{ item['bookmark-of'][0] }}" target="_blank" rel="noopener">
39
+ {{ item['bookmark-of'][0] | replace("https://", "") | replace("http://", "") | truncate(50) }}
40
+ </a>
41
+ </div>
42
+ {% endif %}
43
+
44
+ <a href="{{ baseUrl }}/item/{{ item._id }}" class="item-card__link">
45
+ {# Author #}
4
46
  {% if item.author %}
5
47
  <div class="item-card__author">
6
48
  {% if item.author.photo %}
7
- <img src="{{ item.author.photo }}" alt="" class="item-card__author-photo" width="40" height="40" loading="lazy">
49
+ <img src="{{ item.author.photo }}"
50
+ alt=""
51
+ class="item-card__author-photo"
52
+ width="40"
53
+ height="40"
54
+ loading="lazy"
55
+ onerror="this.style.display='none'">
8
56
  {% endif %}
9
57
  <div class="item-card__author-info">
10
58
  <span class="item-card__author-name">{{ item.author.name or "Unknown" }}</span>
11
59
  {% if item._source %}
12
60
  <span class="item-card__source">{{ item._source.name or item._source.url }}</span>
61
+ {% elif item.author.url %}
62
+ <span class="item-card__source">{{ item.author.url | replace("https://", "") | replace("http://", "") }}</span>
13
63
  {% endif %}
14
64
  </div>
15
65
  </div>
16
66
  {% endif %}
17
67
 
18
- {% if item._type and item._type !== "entry" %}
19
- <div class="item-card__type">
20
- {% if item._type === "like" %}
21
- {{ icon("like") }} Liked
22
- {% elif item._type === "repost" %}
23
- {{ icon("repost") }} Reposted
24
- {% elif item._type === "reply" %}
25
- {{ icon("reply") }} Reply
26
- {% elif item._type === "bookmark" %}
27
- {{ icon("bookmark") }} Bookmarked
28
- {% endif %}
29
- </div>
30
- {% endif %}
31
-
68
+ {# Title (for articles) #}
32
69
  {% if item.name %}
33
70
  <h3 class="item-card__title">{{ item.name }}</h3>
34
71
  {% endif %}
35
72
 
73
+ {# Content with overflow handling #}
36
74
  {% if item.summary or item.content %}
37
- <div class="item-card__content">
38
- {% if item.summary %}
39
- {{ item.summary | truncate(200) }}
75
+ <div class="item-card__content{% if (item.content.text or item.summary or '') | length > 300 %} item-card__content--truncated{% endif %}">
76
+ {% if item.content.html %}
77
+ {{ item.content.html | safe | striptags | truncate(400) }}
40
78
  {% elif item.content.text %}
41
- {{ item.content.text | truncate(200) }}
79
+ {{ item.content.text | truncate(400) }}
80
+ {% elif item.summary %}
81
+ {{ item.summary | truncate(400) }}
42
82
  {% endif %}
43
83
  </div>
44
84
  {% endif %}
45
85
 
86
+ {# Categories/Tags #}
87
+ {% if item.category and item.category.length > 0 %}
88
+ <div class="item-card__categories">
89
+ {% for cat in item.category | slice(0, 5) %}
90
+ <span class="item-card__category">#{{ cat | replace("#", "") }}</span>
91
+ {% endfor %}
92
+ </div>
93
+ {% endif %}
94
+
95
+ {# Photo grid (Aperture multi-photo pattern) #}
46
96
  {% if item.photo and item.photo.length > 0 %}
47
- <div class="item-card__photos">
97
+ <div class="item-card__photos item-card__photos--{{ [item.photo.length, 4] | min }}">
48
98
  {% for photo in item.photo | slice(0, 4) %}
49
- <img src="{{ photo }}" alt="" class="item-card__photo" loading="lazy">
99
+ <img src="{{ photo }}"
100
+ alt=""
101
+ class="item-card__photo"
102
+ loading="lazy"
103
+ onerror="this.parentElement.removeChild(this)">
50
104
  {% endfor %}
51
105
  </div>
52
106
  {% endif %}
53
107
 
108
+ {# Video preview #}
109
+ {% if item.video and item.video.length > 0 %}
110
+ <div class="item-card__media">
111
+ <video src="{{ item.video[0] }}"
112
+ class="item-card__video"
113
+ controls
114
+ preload="metadata"
115
+ {% if item.photo and item.photo.length > 0 %}poster="{{ item.photo[0] }}"{% endif %}>
116
+ </video>
117
+ </div>
118
+ {% endif %}
119
+
120
+ {# Audio preview #}
121
+ {% if item.audio and item.audio.length > 0 %}
122
+ <div class="item-card__media">
123
+ <audio src="{{ item.audio[0] }}" class="item-card__audio" controls preload="metadata"></audio>
124
+ </div>
125
+ {% endif %}
126
+
127
+ {# Footer with date and actions #}
54
128
  <footer class="item-card__footer">
55
129
  {% if item.published %}
56
130
  <time datetime="{{ item.published }}" class="item-card__date">
@@ -62,4 +136,36 @@
62
136
  {% endif %}
63
137
  </footer>
64
138
  </a>
139
+
140
+ {# Inline actions (Aperture pattern) #}
141
+ <div class="item-actions">
142
+ {% if item.url %}
143
+ <a href="{{ item.url }}" class="item-actions__button" target="_blank" rel="noopener" title="View original">
144
+ {{ icon("external") }}
145
+ <span class="visually-hidden">Original</span>
146
+ </a>
147
+ {% endif %}
148
+ <a href="{{ baseUrl }}/compose?reply={{ item.url | urlencode }}" class="item-actions__button" title="Reply">
149
+ {{ icon("reply") }}
150
+ <span class="visually-hidden">Reply</span>
151
+ </a>
152
+ <a href="{{ baseUrl }}/compose?like={{ item.url | urlencode }}" class="item-actions__button" title="Like">
153
+ {{ icon("like") }}
154
+ <span class="visually-hidden">Like</span>
155
+ </a>
156
+ <a href="{{ baseUrl }}/compose?repost={{ item.url | urlencode }}" class="item-actions__button" title="Repost">
157
+ {{ icon("repost") }}
158
+ <span class="visually-hidden">Repost</span>
159
+ </a>
160
+ {% if not item._is_read %}
161
+ <button type="button"
162
+ class="item-actions__button item-actions__mark-read"
163
+ data-action="mark-read"
164
+ data-item-id="{{ item._id }}"
165
+ title="Mark as read">
166
+ {{ icon("checkboxChecked") }}
167
+ <span class="visually-hidden">Mark read</span>
168
+ </button>
169
+ {% endif %}
170
+ </div>
65
171
  </article>
package/views/reader.njk CHANGED
@@ -1,40 +1,41 @@
1
- {% extends "document.njk" %}
1
+ {% extends "layouts/reader.njk" %}
2
2
 
3
- {% block content %}
3
+ {% block reader %}
4
4
  <div class="reader">
5
5
  {% if channels.length > 0 %}
6
- <ul class="reader__channels">
6
+ <div class="reader__channels">
7
7
  {% for channel in channels %}
8
- <li class="reader__channel">
9
- <a href="{{ baseUrl }}/channels/{{ channel.uid }}" class="reader__channel-link">
10
- <span class="reader__channel-name">
11
- {% if channel.uid === "notifications" %}
12
- {{ icon("mention") }}
13
- {% endif %}
14
- {{ channel.name }}
15
- </span>
16
- {% if channel.unread > 0 %}
17
- <span class="reader__channel-badge">{{ channel.unread }}</span>
8
+ <a href="{{ baseUrl }}/channels/{{ channel.uid }}" class="reader__channel{% if channel.uid === currentChannel %} reader__channel--active{% endif %}">
9
+ <span class="reader__channel-name">
10
+ {% if channel.uid === "notifications" %}
11
+ {{ icon("mention") }}
18
12
  {% endif %}
19
- </a>
20
- </li>
13
+ {{ channel.name }}
14
+ </span>
15
+ {% if channel.unread %}
16
+ <span class="reader__channel-badge{% if channel.unread === true %} reader__channel-badge--dot{% endif %}">
17
+ {% if channel.unread !== true %}{{ channel.unread }}{% endif %}
18
+ </span>
19
+ {% endif %}
20
+ </a>
21
21
  {% endfor %}
22
- </ul>
23
- <p class="reader__actions">
22
+ </div>
23
+ <div class="reader__actions">
24
24
  <a href="{{ baseUrl }}/search" class="button button--primary">
25
25
  {{ icon("syndicate") }} {{ __("microsub.feeds.follow") }}
26
26
  </a>
27
27
  <a href="{{ baseUrl }}/channels/new" class="button button--secondary">
28
28
  {{ icon("createPost") }} {{ __("microsub.channels.new") }}
29
29
  </a>
30
- </p>
30
+ </div>
31
31
  {% else %}
32
- {{ prose({ text: __("microsub.channels.empty") }) }}
33
- <p>
32
+ <div class="reader__empty">
33
+ {{ icon("syndicate") }}
34
+ <p>{{ __("microsub.channels.empty") }}</p>
34
35
  <a href="{{ baseUrl }}/channels/new" class="button button--primary">
35
36
  {{ __("microsub.channels.new") }}
36
37
  </a>
37
- </p>
38
+ </div>
38
39
  {% endif %}
39
40
  </div>
40
41
  {% endblock %}
package/views/search.njk CHANGED
@@ -1,6 +1,6 @@
1
- {% extends "document.njk" %}
1
+ {% extends "layouts/reader.njk" %}
2
2
 
3
- {% block content %}
3
+ {% block reader %}
4
4
  <div class="search">
5
5
  <a href="{{ baseUrl }}/channels" class="back-link">
6
6
  {{ icon("previous") }} {{ __("microsub.channels.title") }}
@@ -28,11 +28,12 @@
28
28
  {% if results and results.length > 0 %}
29
29
  <div class="search__results">
30
30
  <h3>{{ __("microsub.search.title") }}</h3>
31
- <ul class="search__list">
31
+ <div class="search__list">
32
32
  {% for result in results %}
33
- <li class="search__item">
33
+ <div class="search__item">
34
34
  <div class="search__feed">
35
- <span class="search__url">{{ result.url }}</span>
35
+ <span class="search__name">{{ result.title or "Feed" }}</span>
36
+ <span class="search__url">{{ result.url | replace("https://", "") | replace("http://", "") }}</span>
36
37
  </div>
37
38
  <form method="post" action="{{ baseUrl }}/subscribe" class="search__subscribe">
38
39
  <input type="hidden" name="url" value="{{ result.url }}">
@@ -47,12 +48,14 @@
47
48
  classes: "button--small"
48
49
  }) }}
49
50
  </form>
50
- </li>
51
+ </div>
51
52
  {% endfor %}
52
- </ul>
53
+ </div>
53
54
  </div>
54
55
  {% elif searched %}
55
- {{ prose({ text: __("microsub.search.noResults") }) }}
56
+ <div class="reader__empty">
57
+ <p>{{ __("microsub.search.noResults") }}</p>
58
+ </div>
56
59
  {% endif %}
57
60
  </div>
58
61
  {% endblock %}
@@ -1,11 +1,13 @@
1
- {% extends "document.njk" %}
1
+ {% extends "layouts/reader.njk" %}
2
2
 
3
- {% block content %}
3
+ {% block reader %}
4
4
  <div class="settings">
5
5
  <a href="{{ baseUrl }}/channels/{{ channel.uid }}" class="back-link">
6
6
  {{ icon("previous") }} {{ channel.name }}
7
7
  </a>
8
8
 
9
+ <h2>{{ __("microsub.settings.title", { channel: channel.name }) }}</h2>
10
+
9
11
  <form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/settings">
10
12
  {{ checkboxes({
11
13
  name: "excludeTypes",