@rmdes/indiekit-endpoint-microsub 1.0.2 → 1.0.4
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 +764 -0
- package/locales/en.json +1 -0
- package/package.json +2 -1
- package/views/channel-new.njk +4 -2
- package/views/channel.njk +62 -4
- package/views/compose.njk +52 -21
- package/views/feeds.njk +20 -9
- package/views/item.njk +91 -25
- package/views/layouts/reader.njk +10 -0
- package/views/partials/item-card.njk +130 -23
- package/views/reader.njk +22 -21
- package/views/search.njk +11 -8
- package/views/settings.njk +4 -2
|
@@ -1,56 +1,131 @@
|
|
|
1
|
-
{#
|
|
2
|
-
|
|
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
|
+
|
|
3
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 }}"
|
|
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
|
-
{
|
|
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.
|
|
39
|
-
{{ item.
|
|
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(
|
|
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
|
-
|
|
97
|
+
{% set photoCount = item.photo.length if item.photo.length <= 4 else 4 %}
|
|
98
|
+
<div class="item-card__photos item-card__photos--{{ photoCount }}">
|
|
48
99
|
{% for photo in item.photo | slice(0, 4) %}
|
|
49
|
-
<img src="{{ photo }}"
|
|
100
|
+
<img src="{{ photo }}"
|
|
101
|
+
alt=""
|
|
102
|
+
class="item-card__photo"
|
|
103
|
+
loading="lazy"
|
|
104
|
+
onerror="this.parentElement.removeChild(this)">
|
|
50
105
|
{% endfor %}
|
|
51
106
|
</div>
|
|
52
107
|
{% endif %}
|
|
53
108
|
|
|
109
|
+
{# Video preview #}
|
|
110
|
+
{% if item.video and item.video.length > 0 %}
|
|
111
|
+
<div class="item-card__media">
|
|
112
|
+
<video src="{{ item.video[0] }}"
|
|
113
|
+
class="item-card__video"
|
|
114
|
+
controls
|
|
115
|
+
preload="metadata"
|
|
116
|
+
{% if item.photo and item.photo.length > 0 %}poster="{{ item.photo[0] }}"{% endif %}>
|
|
117
|
+
</video>
|
|
118
|
+
</div>
|
|
119
|
+
{% endif %}
|
|
120
|
+
|
|
121
|
+
{# Audio preview #}
|
|
122
|
+
{% if item.audio and item.audio.length > 0 %}
|
|
123
|
+
<div class="item-card__media">
|
|
124
|
+
<audio src="{{ item.audio[0] }}" class="item-card__audio" controls preload="metadata"></audio>
|
|
125
|
+
</div>
|
|
126
|
+
{% endif %}
|
|
127
|
+
|
|
128
|
+
{# Footer with date and actions #}
|
|
54
129
|
<footer class="item-card__footer">
|
|
55
130
|
{% if item.published %}
|
|
56
131
|
<time datetime="{{ item.published }}" class="item-card__date">
|
|
@@ -62,4 +137,36 @@
|
|
|
62
137
|
{% endif %}
|
|
63
138
|
</footer>
|
|
64
139
|
</a>
|
|
140
|
+
|
|
141
|
+
{# Inline actions (Aperture pattern) #}
|
|
142
|
+
<div class="item-actions">
|
|
143
|
+
{% if item.url %}
|
|
144
|
+
<a href="{{ item.url }}" class="item-actions__button" target="_blank" rel="noopener" title="View original">
|
|
145
|
+
{{ icon("external") }}
|
|
146
|
+
<span class="visually-hidden">Original</span>
|
|
147
|
+
</a>
|
|
148
|
+
{% endif %}
|
|
149
|
+
<a href="{{ baseUrl }}/compose?reply={{ item.url | urlencode }}" class="item-actions__button" title="Reply">
|
|
150
|
+
{{ icon("reply") }}
|
|
151
|
+
<span class="visually-hidden">Reply</span>
|
|
152
|
+
</a>
|
|
153
|
+
<a href="{{ baseUrl }}/compose?like={{ item.url | urlencode }}" class="item-actions__button" title="Like">
|
|
154
|
+
{{ icon("like") }}
|
|
155
|
+
<span class="visually-hidden">Like</span>
|
|
156
|
+
</a>
|
|
157
|
+
<a href="{{ baseUrl }}/compose?repost={{ item.url | urlencode }}" class="item-actions__button" title="Repost">
|
|
158
|
+
{{ icon("repost") }}
|
|
159
|
+
<span class="visually-hidden">Repost</span>
|
|
160
|
+
</a>
|
|
161
|
+
{% if not item._is_read %}
|
|
162
|
+
<button type="button"
|
|
163
|
+
class="item-actions__button item-actions__mark-read"
|
|
164
|
+
data-action="mark-read"
|
|
165
|
+
data-item-id="{{ item._id }}"
|
|
166
|
+
title="Mark as read">
|
|
167
|
+
{{ icon("checkboxChecked") }}
|
|
168
|
+
<span class="visually-hidden">Mark read</span>
|
|
169
|
+
</button>
|
|
170
|
+
{% endif %}
|
|
171
|
+
</div>
|
|
65
172
|
</article>
|
package/views/reader.njk
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
{% extends "
|
|
1
|
+
{% extends "layouts/reader.njk" %}
|
|
2
2
|
|
|
3
|
-
{% block
|
|
3
|
+
{% block reader %}
|
|
4
4
|
<div class="reader">
|
|
5
5
|
{% if channels.length > 0 %}
|
|
6
|
-
<
|
|
6
|
+
<div class="reader__channels">
|
|
7
7
|
{% for channel in channels %}
|
|
8
|
-
<
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
</
|
|
23
|
-
<
|
|
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
|
-
</
|
|
30
|
+
</div>
|
|
31
31
|
{% else %}
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
</
|
|
38
|
+
</div>
|
|
38
39
|
{% endif %}
|
|
39
40
|
</div>
|
|
40
41
|
{% endblock %}
|
package/views/search.njk
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{% extends "
|
|
1
|
+
{% extends "layouts/reader.njk" %}
|
|
2
2
|
|
|
3
|
-
{% block
|
|
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
|
-
<
|
|
31
|
+
<div class="search__list">
|
|
32
32
|
{% for result in results %}
|
|
33
|
-
<
|
|
33
|
+
<div class="search__item">
|
|
34
34
|
<div class="search__feed">
|
|
35
|
-
<span class="
|
|
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
|
-
</
|
|
51
|
+
</div>
|
|
51
52
|
{% endfor %}
|
|
52
|
-
</
|
|
53
|
+
</div>
|
|
53
54
|
</div>
|
|
54
55
|
{% elif searched %}
|
|
55
|
-
|
|
56
|
+
<div class="reader__empty">
|
|
57
|
+
<p>{{ __("microsub.search.noResults") }}</p>
|
|
58
|
+
</div>
|
|
56
59
|
{% endif %}
|
|
57
60
|
</div>
|
|
58
61
|
{% endblock %}
|
package/views/settings.njk
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
{% extends "
|
|
1
|
+
{% extends "layouts/reader.njk" %}
|
|
2
2
|
|
|
3
|
-
{% block
|
|
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",
|