@rmdes/indiekit-endpoint-webmention-io 1.0.2 → 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/lib/controllers/blocklist.js +1 -24
- package/lib/controllers/dashboard.js +3 -43
- package/lib/storage/blocklist.js +1 -1
- package/lib/storage/webmentions.js +7 -7
- package/lib/sync.js +2 -2
- package/package.json +1 -1
- package/views/webmentions-blocklist.njk +5 -20
- package/views/webmentions.njk +1 -1
|
@@ -5,26 +5,6 @@
|
|
|
5
5
|
import { getBlocklist, unblockDomain } from "../storage/blocklist.js";
|
|
6
6
|
import { unhideByDomain } from "../storage/webmentions.js";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Format a date for display. Returns a human-readable string or null.
|
|
10
|
-
* NEVER pass raw Date objects to Nunjucks templates — the | date filter
|
|
11
|
-
* crashes on both Date objects and undefined/null.
|
|
12
|
-
* @param {*} value
|
|
13
|
-
* @returns {string|null}
|
|
14
|
-
*/
|
|
15
|
-
function formatDate(value) {
|
|
16
|
-
if (!value) return null;
|
|
17
|
-
const d = value instanceof Date ? value : new Date(value);
|
|
18
|
-
if (Number.isNaN(d.getTime())) return null;
|
|
19
|
-
return d.toLocaleDateString("en-GB", {
|
|
20
|
-
year: "numeric",
|
|
21
|
-
month: "short",
|
|
22
|
-
day: "numeric",
|
|
23
|
-
hour: "2-digit",
|
|
24
|
-
minute: "2-digit",
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
8
|
export const blocklistController = {
|
|
29
9
|
/**
|
|
30
10
|
* GET /blocklist - Blocklist management page
|
|
@@ -39,10 +19,7 @@ export const blocklistController = {
|
|
|
39
19
|
if (db) {
|
|
40
20
|
const collection = db.collection("webmentionBlocklist");
|
|
41
21
|
const raw = await getBlocklist(collection);
|
|
42
|
-
entries = raw
|
|
43
|
-
...entry,
|
|
44
|
-
blockedAt: formatDate(entry.blockedAt),
|
|
45
|
-
}));
|
|
22
|
+
entries = raw;
|
|
46
23
|
}
|
|
47
24
|
|
|
48
25
|
response.render("webmentions-blocklist", {
|
|
@@ -15,40 +15,6 @@ import { blockDomain } from "../storage/blocklist.js";
|
|
|
15
15
|
import { getSyncState } from "../sync.js";
|
|
16
16
|
import { getMentionType, getMentionTitle, getAuthorName } from "../utils.js";
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Format a date for direct display in templates (NOT through macros).
|
|
20
|
-
* Returns a human-readable string or null.
|
|
21
|
-
* @param {*} value
|
|
22
|
-
* @returns {string|null}
|
|
23
|
-
*/
|
|
24
|
-
function formatDate(value) {
|
|
25
|
-
if (!value) return null;
|
|
26
|
-
const d = value instanceof Date ? value : new Date(value);
|
|
27
|
-
if (Number.isNaN(d.getTime())) return null;
|
|
28
|
-
return d.toLocaleDateString("en-GB", {
|
|
29
|
-
year: "numeric",
|
|
30
|
-
month: "short",
|
|
31
|
-
day: "numeric",
|
|
32
|
-
hour: "2-digit",
|
|
33
|
-
minute: "2-digit",
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Convert any date value to an ISO 8601 string safe for the Nunjucks | date
|
|
39
|
-
* filter (which calls date-fns parseISO — crashes on Date objects, null, and
|
|
40
|
-
* undefined). Use this for values that pass through mention()/card() macros.
|
|
41
|
-
* @param {*} value
|
|
42
|
-
* @returns {string|null}
|
|
43
|
-
*/
|
|
44
|
-
function toISO(value) {
|
|
45
|
-
if (!value) return null;
|
|
46
|
-
if (value instanceof Date) return value.toISOString();
|
|
47
|
-
const d = new Date(value);
|
|
48
|
-
if (Number.isNaN(d.getTime())) return null;
|
|
49
|
-
return d.toISOString();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
18
|
export const dashboardController = {
|
|
53
19
|
/**
|
|
54
20
|
* GET / - Webmentions dashboard
|
|
@@ -63,10 +29,7 @@ export const dashboardController = {
|
|
|
63
29
|
title: response.locals.__("webmention-io.title"),
|
|
64
30
|
webmentions: [],
|
|
65
31
|
counts: { total: 0, hidden: 0, visible: 0 },
|
|
66
|
-
syncState:
|
|
67
|
-
...getSyncState(),
|
|
68
|
-
lastSync: formatDate(getSyncState().lastSync),
|
|
69
|
-
},
|
|
32
|
+
syncState: getSyncState(),
|
|
70
33
|
cursor: {},
|
|
71
34
|
filter: "all",
|
|
72
35
|
typeFilter: "all",
|
|
@@ -119,7 +82,7 @@ export const dashboardController = {
|
|
|
119
82
|
locale: application.locale,
|
|
120
83
|
title: getMentionTitle({ name: item.name, "wm-property": item.wmProperty }),
|
|
121
84
|
description: html ? { html } : undefined,
|
|
122
|
-
published:
|
|
85
|
+
published: item.published || item.wmReceived,
|
|
123
86
|
url: item.sourceUrl,
|
|
124
87
|
user: {
|
|
125
88
|
avatar: { src: item.authorPhoto },
|
|
@@ -148,10 +111,7 @@ export const dashboardController = {
|
|
|
148
111
|
title: response.locals.__("webmention-io.title"),
|
|
149
112
|
webmentions,
|
|
150
113
|
counts,
|
|
151
|
-
syncState:
|
|
152
|
-
...getSyncState(),
|
|
153
|
-
lastSync: formatDate(getSyncState().lastSync),
|
|
154
|
-
},
|
|
114
|
+
syncState: getSyncState(),
|
|
155
115
|
cursor,
|
|
156
116
|
filter,
|
|
157
117
|
typeFilter,
|
package/lib/storage/blocklist.js
CHANGED
|
@@ -36,7 +36,7 @@ export function jf2ToDocument(item) {
|
|
|
36
36
|
|
|
37
37
|
return {
|
|
38
38
|
wmId: item["wm-id"],
|
|
39
|
-
wmReceived: item["wm-received"]
|
|
39
|
+
wmReceived: item["wm-received"] || new Date().toISOString(),
|
|
40
40
|
wmProperty: item["wm-property"],
|
|
41
41
|
wmTarget: item["wm-target"],
|
|
42
42
|
authorName: item.author?.name || null,
|
|
@@ -44,14 +44,14 @@ export function jf2ToDocument(item) {
|
|
|
44
44
|
authorPhoto: item.author?.photo || null,
|
|
45
45
|
sourceUrl: item.url || null,
|
|
46
46
|
sourceDomain: extractDomain(item.author?.url || item.url || ""),
|
|
47
|
-
published: item.published
|
|
47
|
+
published: item.published || null,
|
|
48
48
|
contentHtml,
|
|
49
49
|
contentText,
|
|
50
50
|
name: item.name || null,
|
|
51
51
|
hidden: false,
|
|
52
52
|
hiddenAt: null,
|
|
53
53
|
hiddenReason: null,
|
|
54
|
-
syncedAt: new Date(),
|
|
54
|
+
syncedAt: new Date().toISOString(),
|
|
55
55
|
raw: item,
|
|
56
56
|
};
|
|
57
57
|
}
|
|
@@ -65,7 +65,7 @@ export function documentToJf2(doc) {
|
|
|
65
65
|
const jf2 = {
|
|
66
66
|
type: "entry",
|
|
67
67
|
"wm-id": doc.wmId,
|
|
68
|
-
"wm-received": doc.wmReceived
|
|
68
|
+
"wm-received": doc.wmReceived,
|
|
69
69
|
"wm-property": doc.wmProperty,
|
|
70
70
|
"wm-target": doc.wmTarget,
|
|
71
71
|
author: {
|
|
@@ -75,7 +75,7 @@ export function documentToJf2(doc) {
|
|
|
75
75
|
photo: doc.authorPhoto || "",
|
|
76
76
|
},
|
|
77
77
|
url: doc.sourceUrl || "",
|
|
78
|
-
published: doc.published
|
|
78
|
+
published: doc.published || doc.wmReceived,
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
if (doc.name) {
|
|
@@ -185,7 +185,7 @@ export async function getMaxWmId(collection) {
|
|
|
185
185
|
export async function hideWebmention(collection, wmId, reason = "manual") {
|
|
186
186
|
await collection.updateOne(
|
|
187
187
|
{ wmId },
|
|
188
|
-
{ $set: { hidden: true, hiddenAt: new Date(), hiddenReason: reason } },
|
|
188
|
+
{ $set: { hidden: true, hiddenAt: new Date().toISOString(), hiddenReason: reason } },
|
|
189
189
|
);
|
|
190
190
|
}
|
|
191
191
|
|
|
@@ -211,7 +211,7 @@ export async function unhideWebmention(collection, wmId) {
|
|
|
211
211
|
export async function hideByDomain(collection, domain, reason = "blocklist") {
|
|
212
212
|
const result = await collection.updateMany(
|
|
213
213
|
{ sourceDomain: domain, hidden: { $ne: true } },
|
|
214
|
-
{ $set: { hidden: true, hiddenAt: new Date(), hiddenReason: reason } },
|
|
214
|
+
{ $set: { hidden: true, hiddenAt: new Date().toISOString(), hiddenReason: reason } },
|
|
215
215
|
);
|
|
216
216
|
return result.modifiedCount;
|
|
217
217
|
}
|
package/lib/sync.js
CHANGED
|
@@ -141,7 +141,7 @@ export async function runSync(dbOrIndiekit, options) {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
syncState.lastSync = new Date();
|
|
144
|
+
syncState.lastSync = new Date().toISOString();
|
|
145
145
|
syncState.syncing = false;
|
|
146
146
|
|
|
147
147
|
console.log(
|
|
@@ -232,7 +232,7 @@ export async function runFullSync(dbOrIndiekit, options) {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
syncState.lastSync = new Date();
|
|
235
|
+
syncState.lastSync = new Date().toISOString();
|
|
236
236
|
syncState.syncing = false;
|
|
237
237
|
|
|
238
238
|
console.log(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-webmention-io",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Webmention moderation endpoint for Indiekit. Syncs webmentions from webmention.io into MongoDB with delete, block, and privacy removal capabilities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|
|
@@ -32,21 +32,6 @@
|
|
|
32
32
|
min-width: 200px;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
.bl-form .field__label {
|
|
36
|
-
display: block;
|
|
37
|
-
font: var(--font-caption, 0.75rem/1.4 sans-serif);
|
|
38
|
-
font-weight: 600;
|
|
39
|
-
margin-block-end: 0.25rem;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.bl-form .field__input {
|
|
43
|
-
width: 100%;
|
|
44
|
-
padding: var(--space-2xs, 0.375rem) var(--space-xs, 0.5rem);
|
|
45
|
-
border: 1px solid var(--color-outline-variant, #ccc);
|
|
46
|
-
border-radius: var(--border-radius-small, 0.25rem);
|
|
47
|
-
font: var(--font-body, 0.875rem/1.5 sans-serif);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
35
|
.bl-table {
|
|
51
36
|
width: 100%;
|
|
52
37
|
border-collapse: collapse;
|
|
@@ -126,8 +111,8 @@
|
|
|
126
111
|
|
|
127
112
|
<form method="post" action="{{ wmEndpoint }}/block" class="bl-form">
|
|
128
113
|
<div class="field">
|
|
129
|
-
<label class="
|
|
130
|
-
<input type="text" name="domain" class="
|
|
114
|
+
<label class="label">{{ __("webmention-io.blocklist.domainLabel") }}</label>
|
|
115
|
+
<input type="text" name="domain" class="input" placeholder="{{ __("webmention-io.blocklist.domainPlaceholder") }}" required>
|
|
131
116
|
</div>
|
|
132
117
|
<button type="submit" class="button button--primary">{{ __("webmention-io.blocklist.blockButton") }}</button>
|
|
133
118
|
</form>
|
|
@@ -141,8 +126,8 @@
|
|
|
141
126
|
<form method="post" action="{{ wmEndpoint }}/privacy-remove" class="bl-form"
|
|
142
127
|
onsubmit="return confirm('This will PERMANENTLY DELETE all webmentions from this domain and block it. This cannot be undone. Continue?')">
|
|
143
128
|
<div class="field">
|
|
144
|
-
<label class="
|
|
145
|
-
<input type="text" name="domain" class="
|
|
129
|
+
<label class="label">{{ __("webmention-io.blocklist.privacy.domainLabel") }}</label>
|
|
130
|
+
<input type="text" name="domain" class="input" placeholder="{{ __("webmention-io.blocklist.domainPlaceholder") }}" required>
|
|
146
131
|
</div>
|
|
147
132
|
<button type="submit" class="button button--primary">{{ __("webmention-io.blocklist.privacy.removeButton") }}</button>
|
|
148
133
|
</form>
|
|
@@ -173,7 +158,7 @@
|
|
|
173
158
|
</span>
|
|
174
159
|
</td>
|
|
175
160
|
<td>{{ entry.mentionsHidden or 0 }}</td>
|
|
176
|
-
<td>{{ entry.blockedAt }}</td>
|
|
161
|
+
<td>{{ entry.blockedAt | date("PPp") }}</td>
|
|
177
162
|
<td>
|
|
178
163
|
<form method="post" action="{{ wmEndpoint }}/blocklist/{{ entry.domain | urlencode }}/delete" style="display:inline">
|
|
179
164
|
<button type="submit" class="button button--small button--secondary"
|
package/views/webmentions.njk
CHANGED
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
<div class="wm-actions-bar">
|
|
140
140
|
<span class="wm-sync-info">
|
|
141
141
|
{% if syncState.lastSync %}
|
|
142
|
-
{{ __("webmention-io.sync.lastSync") }}: {{ syncState.lastSync }}
|
|
142
|
+
{{ __("webmention-io.sync.lastSync") }}: {{ syncState.lastSync | date("PPp") }}
|
|
143
143
|
{% else %}
|
|
144
144
|
{{ __("webmention-io.sync.never") }}
|
|
145
145
|
{% endif %}
|