@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.
@@ -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.map((entry) => ({
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: toISO(item.published || item.wmReceived),
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,
@@ -23,7 +23,7 @@ export async function blockDomain(collection, domain, reason = "spam", mentionsH
23
23
  await collection.insertOne({
24
24
  domain,
25
25
  reason,
26
- blockedAt: new Date(),
26
+ blockedAt: new Date().toISOString(),
27
27
  mentionsHidden,
28
28
  });
29
29
  return true;
@@ -36,7 +36,7 @@ export function jf2ToDocument(item) {
36
36
 
37
37
  return {
38
38
  wmId: item["wm-id"],
39
- wmReceived: item["wm-received"] ? new Date(item["wm-received"]) : new Date(),
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 ? new Date(item.published) : null,
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?.toISOString?.() || 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?.toISOString?.() || doc.published || doc.wmReceived,
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.2",
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="field__label">{{ __("webmention-io.blocklist.domainLabel") }}</label>
130
- <input type="text" name="domain" class="field__input" placeholder="{{ __("webmention-io.blocklist.domainPlaceholder") }}" required>
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="field__label">{{ __("webmention-io.blocklist.privacy.domainLabel") }}</label>
145
- <input type="text" name="domain" class="field__input" placeholder="{{ __("webmention-io.blocklist.domainPlaceholder") }}" required>
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"
@@ -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 %}