@rmdes/indiekit-endpoint-microsub 1.0.0-beta.1
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/README.md +111 -0
- package/index.js +140 -0
- package/lib/cache/redis.js +133 -0
- package/lib/controllers/block.js +85 -0
- package/lib/controllers/channels.js +135 -0
- package/lib/controllers/events.js +56 -0
- package/lib/controllers/follow.js +108 -0
- package/lib/controllers/microsub.js +138 -0
- package/lib/controllers/mute.js +124 -0
- package/lib/controllers/preview.js +67 -0
- package/lib/controllers/reader.js +218 -0
- package/lib/controllers/search.js +142 -0
- package/lib/controllers/timeline.js +117 -0
- package/lib/feeds/atom.js +61 -0
- package/lib/feeds/fetcher.js +205 -0
- package/lib/feeds/hfeed.js +177 -0
- package/lib/feeds/jsonfeed.js +43 -0
- package/lib/feeds/normalizer.js +586 -0
- package/lib/feeds/parser.js +124 -0
- package/lib/feeds/rss.js +61 -0
- package/lib/polling/processor.js +201 -0
- package/lib/polling/scheduler.js +128 -0
- package/lib/polling/tier.js +139 -0
- package/lib/realtime/broker.js +241 -0
- package/lib/search/indexer.js +90 -0
- package/lib/search/query.js +197 -0
- package/lib/storage/channels.js +281 -0
- package/lib/storage/feeds.js +286 -0
- package/lib/storage/filters.js +265 -0
- package/lib/storage/items.js +419 -0
- package/lib/storage/read-state.js +109 -0
- package/lib/utils/jf2.js +170 -0
- package/lib/utils/pagination.js +157 -0
- package/lib/utils/validation.js +217 -0
- package/lib/webmention/processor.js +214 -0
- package/lib/webmention/receiver.js +54 -0
- package/lib/webmention/verifier.js +308 -0
- package/lib/websub/discovery.js +129 -0
- package/lib/websub/handler.js +163 -0
- package/lib/websub/subscriber.js +181 -0
- package/locales/en.json +80 -0
- package/package.json +54 -0
- package/views/channel-new.njk +33 -0
- package/views/channel.njk +41 -0
- package/views/compose.njk +61 -0
- package/views/item.njk +85 -0
- package/views/partials/actions.njk +15 -0
- package/views/partials/author.njk +17 -0
- package/views/partials/item-card.njk +65 -0
- package/views/partials/timeline.njk +10 -0
- package/views/reader.njk +37 -0
- package/views/settings.njk +81 -0
package/views/item.njk
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{% extends "document.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<article class="item">
|
|
5
|
+
<a href="{{ request.headers.referer or request.baseUrl + '/channels' }}" class="back-link">
|
|
6
|
+
{{ icon("arrow-left") }} {{ __("Back") }}
|
|
7
|
+
</a>
|
|
8
|
+
|
|
9
|
+
{% if item.author %}
|
|
10
|
+
<header class="item__author">
|
|
11
|
+
{% if item.author.photo %}
|
|
12
|
+
<img src="{{ item.author.photo }}" alt="" class="item__author-photo" width="48" height="48" loading="lazy">
|
|
13
|
+
{% endif %}
|
|
14
|
+
<div class="item__author-info">
|
|
15
|
+
<span class="item__author-name">
|
|
16
|
+
{% if item.author.url %}
|
|
17
|
+
<a href="{{ item.author.url }}">{{ item.author.name or item.author.url }}</a>
|
|
18
|
+
{% else %}
|
|
19
|
+
{{ item.author.name or "Unknown" }}
|
|
20
|
+
{% endif %}
|
|
21
|
+
</span>
|
|
22
|
+
{% if item.published %}
|
|
23
|
+
<time datetime="{{ item.published }}" class="item__date">
|
|
24
|
+
{{ item.published | date("PPPp", { locale: locale, timeZone: application.timeZone }) }}
|
|
25
|
+
</time>
|
|
26
|
+
{% endif %}
|
|
27
|
+
</div>
|
|
28
|
+
</header>
|
|
29
|
+
{% endif %}
|
|
30
|
+
|
|
31
|
+
{% if item.name %}
|
|
32
|
+
<h2 class="item__title">{{ item.name }}</h2>
|
|
33
|
+
{% endif %}
|
|
34
|
+
|
|
35
|
+
{% if item.content %}
|
|
36
|
+
<div class="item__content prose">
|
|
37
|
+
{% if item.content.html %}
|
|
38
|
+
{{ item.content.html | safe }}
|
|
39
|
+
{% else %}
|
|
40
|
+
{{ item.content.text }}
|
|
41
|
+
{% endif %}
|
|
42
|
+
</div>
|
|
43
|
+
{% endif %}
|
|
44
|
+
|
|
45
|
+
{% if item.photo and item.photo.length > 0 %}
|
|
46
|
+
<div class="item__photos">
|
|
47
|
+
{% for photo in item.photo %}
|
|
48
|
+
<img src="{{ photo }}" alt="" class="item__photo" loading="lazy">
|
|
49
|
+
{% endfor %}
|
|
50
|
+
</div>
|
|
51
|
+
{% endif %}
|
|
52
|
+
|
|
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("heart") }} {{ __("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 %}
|
|
67
|
+
</div>
|
|
68
|
+
{% endif %}
|
|
69
|
+
|
|
70
|
+
<footer class="item__actions">
|
|
71
|
+
<a href="{{ request.baseUrl }}/compose?replyTo={{ item.url | urlencode }}" class="button button--secondary button--small">
|
|
72
|
+
{{ icon("reply") }} {{ __("microsub.item.reply") }}
|
|
73
|
+
</a>
|
|
74
|
+
<a href="{{ request.baseUrl }}/compose?likeOf={{ item.url | urlencode }}" class="button button--secondary button--small">
|
|
75
|
+
{{ icon("heart") }} {{ __("microsub.item.like") }}
|
|
76
|
+
</a>
|
|
77
|
+
<a href="{{ request.baseUrl }}/compose?repostOf={{ item.url | urlencode }}" class="button button--secondary button--small">
|
|
78
|
+
{{ icon("repost") }} {{ __("microsub.item.repost") }}
|
|
79
|
+
</a>
|
|
80
|
+
<a href="{{ item.url }}" class="button button--secondary button--small" target="_blank" rel="noopener">
|
|
81
|
+
{{ icon("external") }} {{ __("microsub.item.viewOriginal") }}
|
|
82
|
+
</a>
|
|
83
|
+
</footer>
|
|
84
|
+
</article>
|
|
85
|
+
{% endblock %}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{# Item action buttons #}
|
|
2
|
+
<div class="item-actions">
|
|
3
|
+
<a href="{{ request.baseUrl }}/compose?replyTo={{ itemUrl | urlencode }}" class="item-actions__button" title="{{ __('microsub.item.reply') }}">
|
|
4
|
+
{{ icon("reply") }}
|
|
5
|
+
</a>
|
|
6
|
+
<a href="{{ request.baseUrl }}/compose?likeOf={{ itemUrl | urlencode }}" class="item-actions__button" title="{{ __('microsub.item.like') }}">
|
|
7
|
+
{{ icon("heart") }}
|
|
8
|
+
</a>
|
|
9
|
+
<a href="{{ request.baseUrl }}/compose?repostOf={{ itemUrl | urlencode }}" class="item-actions__button" title="{{ __('microsub.item.repost') }}">
|
|
10
|
+
{{ icon("repost") }}
|
|
11
|
+
</a>
|
|
12
|
+
<a href="{{ itemUrl }}" class="item-actions__button" target="_blank" rel="noopener" title="{{ __('microsub.item.viewOriginal') }}">
|
|
13
|
+
{{ icon("external") }}
|
|
14
|
+
</a>
|
|
15
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{# Author display #}
|
|
2
|
+
{% if author %}
|
|
3
|
+
<div class="author">
|
|
4
|
+
{% if author.photo %}
|
|
5
|
+
<img src="{{ author.photo }}" alt="" class="author__photo" width="48" height="48" loading="lazy">
|
|
6
|
+
{% endif %}
|
|
7
|
+
<div class="author__info">
|
|
8
|
+
<span class="author__name">
|
|
9
|
+
{% if author.url %}
|
|
10
|
+
<a href="{{ author.url }}">{{ author.name or author.url }}</a>
|
|
11
|
+
{% else %}
|
|
12
|
+
{{ author.name or "Unknown" }}
|
|
13
|
+
{% endif %}
|
|
14
|
+
</span>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
{% endif %}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{# Item card for timeline display #}
|
|
2
|
+
<article class="item-card{% if item._is_read %} item-card--read{% endif %}">
|
|
3
|
+
<a href="{{ request.baseUrl }}/item/{{ item.uid }}" class="item-card__link">
|
|
4
|
+
{% if item.author %}
|
|
5
|
+
<div class="item-card__author">
|
|
6
|
+
{% if item.author.photo %}
|
|
7
|
+
<img src="{{ item.author.photo }}" alt="" class="item-card__author-photo" width="40" height="40" loading="lazy">
|
|
8
|
+
{% endif %}
|
|
9
|
+
<div class="item-card__author-info">
|
|
10
|
+
<span class="item-card__author-name">{{ item.author.name or "Unknown" }}</span>
|
|
11
|
+
{% if item._source %}
|
|
12
|
+
<span class="item-card__source">{{ item._source.name or item._source.url }}</span>
|
|
13
|
+
{% endif %}
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
{% endif %}
|
|
17
|
+
|
|
18
|
+
{% if item._type and item._type !== "entry" %}
|
|
19
|
+
<div class="item-card__type">
|
|
20
|
+
{% if item._type === "like" %}
|
|
21
|
+
{{ icon("heart") }} 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
|
+
|
|
32
|
+
{% if item.name %}
|
|
33
|
+
<h3 class="item-card__title">{{ item.name }}</h3>
|
|
34
|
+
{% endif %}
|
|
35
|
+
|
|
36
|
+
{% if item.summary or item.content %}
|
|
37
|
+
<div class="item-card__content">
|
|
38
|
+
{% if item.summary %}
|
|
39
|
+
{{ item.summary | truncate(200) }}
|
|
40
|
+
{% elif item.content.text %}
|
|
41
|
+
{{ item.content.text | truncate(200) }}
|
|
42
|
+
{% endif %}
|
|
43
|
+
</div>
|
|
44
|
+
{% endif %}
|
|
45
|
+
|
|
46
|
+
{% if item.photo and item.photo.length > 0 %}
|
|
47
|
+
<div class="item-card__photos">
|
|
48
|
+
{% for photo in item.photo | slice(0, 4) %}
|
|
49
|
+
<img src="{{ photo }}" alt="" class="item-card__photo" loading="lazy">
|
|
50
|
+
{% endfor %}
|
|
51
|
+
</div>
|
|
52
|
+
{% endif %}
|
|
53
|
+
|
|
54
|
+
<footer class="item-card__footer">
|
|
55
|
+
{% if item.published %}
|
|
56
|
+
<time datetime="{{ item.published }}" class="item-card__date">
|
|
57
|
+
{{ item.published | date("PP", { locale: locale, timeZone: application.timeZone }) }}
|
|
58
|
+
</time>
|
|
59
|
+
{% endif %}
|
|
60
|
+
{% if not item._is_read %}
|
|
61
|
+
<span class="item-card__unread">{{ icon("dot") }}</span>
|
|
62
|
+
{% endif %}
|
|
63
|
+
</footer>
|
|
64
|
+
</a>
|
|
65
|
+
</article>
|
package/views/reader.njk
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{% extends "document.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<div class="reader">
|
|
5
|
+
{% if channels.length > 0 %}
|
|
6
|
+
<ul class="reader__channels">
|
|
7
|
+
{% for channel in channels %}
|
|
8
|
+
<li class="reader__channel">
|
|
9
|
+
<a href="{{ request.baseUrl }}/channels/{{ channel.uid }}" class="reader__channel-link">
|
|
10
|
+
<span class="reader__channel-name">
|
|
11
|
+
{% if channel.uid === "notifications" %}
|
|
12
|
+
{{ icon("bell") }}
|
|
13
|
+
{% endif %}
|
|
14
|
+
{{ channel.name }}
|
|
15
|
+
</span>
|
|
16
|
+
{% if channel.unread > 0 %}
|
|
17
|
+
<span class="reader__channel-badge">{{ channel.unread }}</span>
|
|
18
|
+
{% endif %}
|
|
19
|
+
</a>
|
|
20
|
+
</li>
|
|
21
|
+
{% endfor %}
|
|
22
|
+
</ul>
|
|
23
|
+
<p class="reader__actions">
|
|
24
|
+
<a href="{{ request.baseUrl }}/channels/new" class="button button--secondary">
|
|
25
|
+
{{ icon("plus") }} {{ __("microsub.channels.new") }}
|
|
26
|
+
</a>
|
|
27
|
+
</p>
|
|
28
|
+
{% else %}
|
|
29
|
+
{{ prose({ text: __("microsub.channels.empty") }) }}
|
|
30
|
+
<p>
|
|
31
|
+
<a href="{{ request.baseUrl }}/channels/new" class="button button--primary">
|
|
32
|
+
{{ __("microsub.channels.new") }}
|
|
33
|
+
</a>
|
|
34
|
+
</p>
|
|
35
|
+
{% endif %}
|
|
36
|
+
</div>
|
|
37
|
+
{% endblock %}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{% extends "document.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<div class="settings">
|
|
5
|
+
<a href="{{ request.baseUrl }}/channels/{{ channel.uid }}" class="back-link">
|
|
6
|
+
{{ icon("arrow-left") }} {{ channel.name }}
|
|
7
|
+
</a>
|
|
8
|
+
|
|
9
|
+
<form method="post" action="{{ request.baseUrl }}/channels/{{ channel.uid }}/settings">
|
|
10
|
+
{{ fieldset({
|
|
11
|
+
legend: {
|
|
12
|
+
text: __("microsub.settings.excludeTypes"),
|
|
13
|
+
classes: "fieldset__legend--medium"
|
|
14
|
+
},
|
|
15
|
+
hint: {
|
|
16
|
+
text: __("microsub.settings.excludeTypesHelp")
|
|
17
|
+
},
|
|
18
|
+
items: [
|
|
19
|
+
{
|
|
20
|
+
id: "excludeTypes-like",
|
|
21
|
+
name: "excludeTypes",
|
|
22
|
+
value: "like",
|
|
23
|
+
text: __("microsub.settings.types.like"),
|
|
24
|
+
checked: channel.settings.excludeTypes | includes("like")
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "excludeTypes-repost",
|
|
28
|
+
name: "excludeTypes",
|
|
29
|
+
value: "repost",
|
|
30
|
+
text: __("microsub.settings.types.repost"),
|
|
31
|
+
checked: channel.settings.excludeTypes | includes("repost")
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "excludeTypes-bookmark",
|
|
35
|
+
name: "excludeTypes",
|
|
36
|
+
value: "bookmark",
|
|
37
|
+
text: __("microsub.settings.types.bookmark"),
|
|
38
|
+
checked: channel.settings.excludeTypes | includes("bookmark")
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "excludeTypes-reply",
|
|
42
|
+
name: "excludeTypes",
|
|
43
|
+
value: "reply",
|
|
44
|
+
text: __("microsub.settings.types.reply"),
|
|
45
|
+
checked: channel.settings.excludeTypes | includes("reply")
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "excludeTypes-checkin",
|
|
49
|
+
name: "excludeTypes",
|
|
50
|
+
value: "checkin",
|
|
51
|
+
text: __("microsub.settings.types.checkin"),
|
|
52
|
+
checked: channel.settings.excludeTypes | includes("checkin")
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}) | checkboxes }}
|
|
56
|
+
|
|
57
|
+
{{ field({
|
|
58
|
+
label: {
|
|
59
|
+
text: __("microsub.settings.excludeRegex")
|
|
60
|
+
},
|
|
61
|
+
hint: {
|
|
62
|
+
text: __("microsub.settings.excludeRegexHelp")
|
|
63
|
+
},
|
|
64
|
+
input: {
|
|
65
|
+
id: "excludeRegex",
|
|
66
|
+
name: "excludeRegex",
|
|
67
|
+
value: channel.settings.excludeRegex
|
|
68
|
+
}
|
|
69
|
+
}) }}
|
|
70
|
+
|
|
71
|
+
<div class="button-group">
|
|
72
|
+
{{ button({
|
|
73
|
+
text: __("microsub.settings.save")
|
|
74
|
+
}) }}
|
|
75
|
+
<a href="{{ request.baseUrl }}/channels/{{ channel.uid }}" class="button button--secondary">
|
|
76
|
+
{{ __("Cancel") }}
|
|
77
|
+
</a>
|
|
78
|
+
</div>
|
|
79
|
+
</form>
|
|
80
|
+
</div>
|
|
81
|
+
{% endblock %}
|