@rmdes/indiekit-endpoint-rss 1.1.1 → 1.1.2
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 +15 -6
- package/lib/controllers/feeds.js +27 -2
- package/lib/sync.js +16 -1
- package/locales/en.json +7 -2
- package/package.json +1 -1
- package/views/rss.njk +78 -69
package/assets/styles.css
CHANGED
|
@@ -83,19 +83,28 @@
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
.rss-feed-item {
|
|
86
|
-
align-items:
|
|
86
|
+
align-items: flex-start;
|
|
87
87
|
border-block-end: 1px solid var(--color-border);
|
|
88
88
|
display: flex;
|
|
89
|
-
flex-wrap: wrap;
|
|
90
89
|
gap: var(--space-s);
|
|
91
90
|
padding: var(--space-s) 0;
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
/* The publish disclosure
|
|
95
|
-
|
|
96
|
-
feed
|
|
93
|
+
/* The publish disclosure lives inside the feed's own column. As a full-width
|
|
94
|
+
flex row it turned every feed into three rows and put a bar wider than the
|
|
95
|
+
feed between each entry, which made the list unscannable. Its summary stays
|
|
96
|
+
quiet so the feed title remains what the eye lands on. */
|
|
97
97
|
.rss-publish {
|
|
98
|
-
|
|
98
|
+
margin-block-start: var(--space-2xs);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.rss-publish .details__summary {
|
|
102
|
+
color: var(--color-text-secondary);
|
|
103
|
+
font-size: var(--step--1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.rss-publish .details__main {
|
|
107
|
+
padding-block-start: var(--space-2xs);
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
.rss-feed-item:last-child {
|
package/lib/controllers/feeds.js
CHANGED
|
@@ -156,13 +156,13 @@ export const feedsController = {
|
|
|
156
156
|
async toggle(request, response) {
|
|
157
157
|
try {
|
|
158
158
|
const { id } = request.params;
|
|
159
|
-
const { enabled, publish } = request.body;
|
|
159
|
+
const { enabled, publish, url } = request.body;
|
|
160
160
|
|
|
161
161
|
if (!ObjectId.isValid(id)) {
|
|
162
162
|
return response.status(400).json({ error: "Invalid feed ID" });
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
if (enabled === undefined && publish === undefined) {
|
|
165
|
+
if (enabled === undefined && publish === undefined && url === undefined) {
|
|
166
166
|
return response.status(400).json({ error: "Nothing to update" });
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -191,6 +191,31 @@ export const feedsController = {
|
|
|
191
191
|
update.enabled = enabled;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
if (url !== undefined) {
|
|
195
|
+
if (!isValidUrl(url)) {
|
|
196
|
+
return response.status(400).json({
|
|
197
|
+
error: response.locals.__("rss.error.invalidUrl"),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const normalizedUrl = normalizeUrl(url);
|
|
202
|
+
const clash = await feedsCollection.findOne({
|
|
203
|
+
url: normalizedUrl,
|
|
204
|
+
_id: { $ne: feedId },
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (clash) {
|
|
208
|
+
return response.status(409).json({
|
|
209
|
+
error: response.locals.__("rss.error.feedExists"),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
update.url = normalizedUrl;
|
|
214
|
+
// Items key on feedId, so they survive the change. Any error from the
|
|
215
|
+
// old URL is stale the moment it is corrected.
|
|
216
|
+
update.lastError = null;
|
|
217
|
+
}
|
|
218
|
+
|
|
194
219
|
if (publish !== undefined) {
|
|
195
220
|
const { postTypes } = request.app.locals.publication || {};
|
|
196
221
|
|
package/lib/sync.js
CHANGED
|
@@ -173,7 +173,7 @@ export async function runSync(dbOrIndiekit, options, publishContext) {
|
|
|
173
173
|
itemsCollection,
|
|
174
174
|
feedsCollection,
|
|
175
175
|
retentionDays,
|
|
176
|
-
options
|
|
176
|
+
retentionFloor(options),
|
|
177
177
|
);
|
|
178
178
|
|
|
179
179
|
syncState.lastSync = new Date().toISOString();
|
|
@@ -310,6 +310,21 @@ async function createIndexes(feedsCollection, itemsCollection) {
|
|
|
310
310
|
await itemsCollection.createIndex({ fetchedAt: -1 });
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Number of newest items per feed that pruning must never touch.
|
|
315
|
+
*
|
|
316
|
+
* Pruning an item the feed still serves is a no-op by construction — the next
|
|
317
|
+
* sync re-inserts it. A sync stores at most `maxItemsPerFeed` items per feed,
|
|
318
|
+
* so age-based retention below that threshold can only churn: insert, delete,
|
|
319
|
+
* refetch, forever. The floor is therefore derived from what a sync can store,
|
|
320
|
+
* not set as an independent constant.
|
|
321
|
+
* @param {object} options - Plugin options
|
|
322
|
+
* @returns {number} Items to keep per feed regardless of age
|
|
323
|
+
*/
|
|
324
|
+
export function retentionFloor(options = {}) {
|
|
325
|
+
return Math.max(options.minItemsPerFeed ?? 10, options.maxItemsPerFeed ?? 50);
|
|
326
|
+
}
|
|
327
|
+
|
|
313
328
|
/**
|
|
314
329
|
* Rewrite legacy BSON Date values of `addedAt` as ISO 8601 strings.
|
|
315
330
|
*
|
package/locales/en.json
CHANGED
|
@@ -56,13 +56,18 @@
|
|
|
56
56
|
"enabled": "Create posts from this feed",
|
|
57
57
|
"postType": "Post type",
|
|
58
58
|
"content": "Content template",
|
|
59
|
-
"contentHint": "
|
|
59
|
+
"contentHint": "Available: title, link, description, content, author, sourceTitle. Write each one in double braces, as shown below.",
|
|
60
60
|
"linkProperty": "Link property",
|
|
61
61
|
"linkPropertyHint": "Discovery property carrying the item URL, e.g. bookmark-of. Leave empty for a plain note or article.",
|
|
62
62
|
"status": "Post status",
|
|
63
63
|
"draft": "Draft",
|
|
64
64
|
"published": "Published",
|
|
65
|
-
"save": "Save
|
|
65
|
+
"save": "Save settings"
|
|
66
|
+
},
|
|
67
|
+
"settings": {
|
|
68
|
+
"title": "Feed settings",
|
|
69
|
+
"url": "Feed URL",
|
|
70
|
+
"urlHint": "Correcting this keeps the cached items; removing and re-adding the feed would lose them."
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
}
|
package/package.json
CHANGED
package/views/rss.njk
CHANGED
|
@@ -100,79 +100,88 @@
|
|
|
100
100
|
{% if feed.lastError %}
|
|
101
101
|
<div class="rss-feed-error">{{ feed.lastError }}</div>
|
|
102
102
|
{% endif %}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
103
|
+
{% set typeItems = [] %}
|
|
104
|
+
{% for type in postTypes %}
|
|
105
|
+
{% set typeItems = (typeItems.push({
|
|
106
|
+
text: type,
|
|
107
|
+
value: type,
|
|
108
|
+
selected: feed.publish.postType == type
|
|
109
|
+
}), typeItems) %}
|
|
110
|
+
{% endfor %}
|
|
111
|
+
{% set publishSummary %}{{ __("rss.settings.title") }}{% if feed.publish.enabled %} • {{ feed.publish.postType }}{% endif %}{% endset %}
|
|
112
|
+
{% call details({
|
|
113
|
+
classes: "rss-publish",
|
|
114
|
+
summary: publishSummary
|
|
115
|
+
}) %}
|
|
116
|
+
<form data-publish-feed="{{ feed.id }}">
|
|
117
|
+
{{ input({
|
|
118
|
+
id: "feed-url-" + feed.id,
|
|
119
|
+
name: "url",
|
|
120
|
+
type: "url",
|
|
121
|
+
label: __("rss.settings.url"),
|
|
122
|
+
hint: __("rss.settings.urlHint"),
|
|
123
|
+
value: feed.url
|
|
124
|
+
}) }}
|
|
125
|
+
|
|
126
|
+
{{ checkboxes({
|
|
127
|
+
idPrefix: "publish-enabled-" + feed.id,
|
|
128
|
+
name: "enabled",
|
|
129
|
+
items: [{
|
|
130
|
+
value: "true",
|
|
131
|
+
label: __("rss.publish.enabled"),
|
|
132
|
+
checked: feed.publish.enabled
|
|
133
|
+
}]
|
|
134
|
+
}) }}
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
{{ select({
|
|
137
|
+
id: "publish-post-type-" + feed.id,
|
|
138
|
+
name: "postType",
|
|
139
|
+
label: __("rss.publish.postType"),
|
|
140
|
+
items: typeItems
|
|
141
|
+
}) }}
|
|
134
142
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
{{ input({
|
|
144
|
+
id: "publish-content-" + feed.id,
|
|
145
|
+
name: "content",
|
|
146
|
+
label: __("rss.publish.content"),
|
|
147
|
+
hint: __("rss.publish.contentHint"),
|
|
148
|
+
value: feed.publish.content or "{{description}}"
|
|
149
|
+
}) }}
|
|
142
150
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
{{ input({
|
|
152
|
+
id: "publish-link-property-" + feed.id,
|
|
153
|
+
name: "linkProperty",
|
|
154
|
+
label: __("rss.publish.linkProperty"),
|
|
155
|
+
hint: __("rss.publish.linkPropertyHint"),
|
|
156
|
+
optional: true,
|
|
157
|
+
value: feed.publish.linkProperty
|
|
158
|
+
}) }}
|
|
151
159
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
160
|
+
{{ select({
|
|
161
|
+
id: "publish-status-" + feed.id,
|
|
162
|
+
name: "status",
|
|
163
|
+
label: __("rss.publish.status"),
|
|
164
|
+
items: [
|
|
165
|
+
{
|
|
166
|
+
text: __("rss.publish.draft"),
|
|
167
|
+
value: "draft",
|
|
168
|
+
selected: feed.publish.status != "published"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
text: __("rss.publish.published"),
|
|
172
|
+
value: "published",
|
|
173
|
+
selected: feed.publish.status == "published"
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}) }}
|
|
169
177
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
{{ button({
|
|
179
|
+
type: "submit",
|
|
180
|
+
text: __("rss.publish.save")
|
|
181
|
+
}) }}
|
|
182
|
+
</form>
|
|
183
|
+
{% endcall %}
|
|
184
|
+
</div>
|
|
176
185
|
<div class="rss-feed-actions">
|
|
177
186
|
<label class="rss-toggle">
|
|
178
187
|
<input
|
|
@@ -374,7 +383,7 @@
|
|
|
374
383
|
const response = await fetch(`{{ mountPath }}/api/feeds/${feedId}`, {
|
|
375
384
|
method: 'PATCH',
|
|
376
385
|
headers: { 'Content-Type': 'application/json' },
|
|
377
|
-
body: JSON.stringify({ publish })
|
|
386
|
+
body: JSON.stringify({ url: data.get('url'), publish })
|
|
378
387
|
});
|
|
379
388
|
|
|
380
389
|
if (response.ok) {
|