@rmdes/indiekit-endpoint-rss 1.1.1 → 1.1.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/assets/styles.css +15 -6
- package/lib/controllers/feeds.js +42 -8
- package/lib/publisher.js +30 -16
- package/lib/sync.js +16 -1
- package/locales/en.json +12 -2
- package/package.json +1 -1
- package/views/rss.njk +144 -74
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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ObjectId } from "mongodb";
|
|
2
2
|
import { RssClient } from "../rss-client.js";
|
|
3
3
|
import { defaultContent, defaultLinkProperty } from "../jf2-builder.js";
|
|
4
|
-
import { watermarkFor } from "../publisher.js";
|
|
5
4
|
import { formatFeed, isValidUrl, normalizeUrl } from "../utils.js";
|
|
5
|
+
import { pendingQuery, watermarkFor } from "../publisher.js";
|
|
6
6
|
|
|
7
7
|
export const feedsController = {
|
|
8
8
|
/**
|
|
@@ -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
|
|
|
@@ -238,7 +263,17 @@ export const feedsController = {
|
|
|
238
263
|
{ returnDocument: "after" },
|
|
239
264
|
);
|
|
240
265
|
|
|
266
|
+
// Enabling publishing stamps a watermark, so a feed can be correctly
|
|
267
|
+
// configured and still have nothing to publish. Saying so beats silence.
|
|
268
|
+
let eligible;
|
|
269
|
+
if (result.publish?.enabled) {
|
|
270
|
+
eligible = await db
|
|
271
|
+
.collection("rssItems")
|
|
272
|
+
.countDocuments(pendingQuery(result));
|
|
273
|
+
}
|
|
274
|
+
|
|
241
275
|
response.json({
|
|
276
|
+
eligible,
|
|
242
277
|
message:
|
|
243
278
|
enabled === undefined
|
|
244
279
|
? response.locals.__("rss.success.feedUpdated")
|
|
@@ -309,11 +344,10 @@ export const feedsController = {
|
|
|
309
344
|
await feedsCollection.updateOne(
|
|
310
345
|
{ _id: feedId },
|
|
311
346
|
{
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
},
|
|
347
|
+
// The feed's own post status is respected: silently forcing drafts
|
|
348
|
+
// would override a deliberate choice. The guard here is the count
|
|
349
|
+
// the user asked for, and maxPostsPerCycle pacing the catch-up.
|
|
350
|
+
$set: { "publish.since": watermark },
|
|
317
351
|
},
|
|
318
352
|
);
|
|
319
353
|
|
package/lib/publisher.js
CHANGED
|
@@ -102,6 +102,35 @@ export function resolvePublishContext(application = {}, publication = {}) {
|
|
|
102
102
|
|
|
103
103
|
const MAX_ATTEMPTS = 3;
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Query selecting a feed's items that are still waiting to be published.
|
|
107
|
+
*
|
|
108
|
+
* Exported so the dashboard can count them with exactly the same rule the
|
|
109
|
+
* publish loop applies. Two copies of this would drift, and the symptom would
|
|
110
|
+
* be a count that disagrees with what actually publishes.
|
|
111
|
+
* @param {object} feed - Feed document
|
|
112
|
+
* @returns {object} MongoDB query
|
|
113
|
+
*/
|
|
114
|
+
export function pendingQuery(feed) {
|
|
115
|
+
const query = {
|
|
116
|
+
feedId: feed._id,
|
|
117
|
+
postedAt: { $exists: false },
|
|
118
|
+
postSkipped: { $ne: true },
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (feed.publish?.since) {
|
|
122
|
+
// An item with no parsable date would be excluded forever by a bare
|
|
123
|
+
// `pubDate: { $gt: since }`, because null sorts below every Date in BSON.
|
|
124
|
+
// fetchedAt is always set on insert and stands in when pubDate is missing.
|
|
125
|
+
query.$or = [
|
|
126
|
+
{ pubDate: { $gt: feed.publish.since } },
|
|
127
|
+
{ pubDate: null, fetchedAt: { $gt: feed.publish.since } },
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return query;
|
|
132
|
+
}
|
|
133
|
+
|
|
105
134
|
/**
|
|
106
135
|
* Publish the pending items of one feed.
|
|
107
136
|
*
|
|
@@ -131,22 +160,7 @@ export async function publishPending(feed, itemsCollection, options) {
|
|
|
131
160
|
mintImpl = mintToken,
|
|
132
161
|
} = options;
|
|
133
162
|
|
|
134
|
-
const query =
|
|
135
|
-
feedId: feed._id,
|
|
136
|
-
postedAt: { $exists: false },
|
|
137
|
-
postSkipped: { $ne: true },
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
if (feed.publish.since) {
|
|
141
|
-
// An item with no parsable date would be excluded forever by a bare
|
|
142
|
-
// `pubDate: { $gt: since }`, because null sorts below every Date in BSON —
|
|
143
|
-
// a silent permanent no-op. fetchedAt is always set on insert and is
|
|
144
|
-
// monotonic with cache entry, so it stands in when pubDate is missing.
|
|
145
|
-
query.$or = [
|
|
146
|
-
{ pubDate: { $gt: feed.publish.since } },
|
|
147
|
-
{ pubDate: null, fetchedAt: { $gt: feed.publish.since } },
|
|
148
|
-
];
|
|
149
|
-
}
|
|
163
|
+
const query = pendingQuery(feed);
|
|
150
164
|
|
|
151
165
|
const items = await itemsCollection
|
|
152
166
|
.find(query)
|
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,23 @@
|
|
|
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."
|
|
71
|
+
},
|
|
72
|
+
"backfill": {
|
|
73
|
+
"label": "Publish existing items",
|
|
74
|
+
"hint": "How many of the newest cached items to publish. They are created with this feed's post status, a few per sync cycle.",
|
|
75
|
+
"submit": "Publish existing items"
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
}
|
package/package.json
CHANGED
package/views/rss.njk
CHANGED
|
@@ -100,79 +100,106 @@
|
|
|
100
100
|
{% if feed.lastError %}
|
|
101
101
|
<div class="rss-feed-error">{{ feed.lastError }}</div>
|
|
102
102
|
{% endif %}
|
|
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
|
+
}) }}
|
|
135
|
+
|
|
136
|
+
{{ select({
|
|
137
|
+
id: "publish-post-type-" + feed.id,
|
|
138
|
+
name: "postType",
|
|
139
|
+
label: __("rss.publish.postType"),
|
|
140
|
+
items: typeItems
|
|
141
|
+
}) }}
|
|
142
|
+
|
|
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
|
+
}) }}
|
|
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
|
+
}) }}
|
|
159
|
+
|
|
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
|
+
}) }}
|
|
177
|
+
|
|
178
|
+
{{ button({
|
|
179
|
+
type: "submit",
|
|
180
|
+
text: __("rss.publish.save")
|
|
181
|
+
}) }}
|
|
182
|
+
</form>
|
|
183
|
+
|
|
184
|
+
{% if feed.publish.enabled %}
|
|
185
|
+
<form class="rss-backfill" data-backfill-feed="{{ feed.id }}">
|
|
186
|
+
{{ input({
|
|
187
|
+
id: "backfill-last-" + feed.id,
|
|
188
|
+
name: "last",
|
|
189
|
+
type: "number",
|
|
190
|
+
label: __("rss.backfill.label"),
|
|
191
|
+
hint: __("rss.backfill.hint"),
|
|
192
|
+
value: "10"
|
|
193
|
+
}) }}
|
|
194
|
+
{{ button({
|
|
195
|
+
classes: "button--secondary",
|
|
196
|
+
type: "submit",
|
|
197
|
+
text: __("rss.backfill.submit")
|
|
198
|
+
}) }}
|
|
199
|
+
</form>
|
|
200
|
+
{% endif %}
|
|
201
|
+
{% endcall %}
|
|
103
202
|
</div>
|
|
104
|
-
{% set typeItems = [] %}
|
|
105
|
-
{% for type in postTypes %}
|
|
106
|
-
{% set typeItems = (typeItems.push({
|
|
107
|
-
text: type,
|
|
108
|
-
value: type,
|
|
109
|
-
selected: feed.publish.postType == type
|
|
110
|
-
}), typeItems) %}
|
|
111
|
-
{% endfor %}
|
|
112
|
-
{% set publishSummary %}{{ __("rss.publish.title") }}{% if feed.publish.enabled %} • {{ feed.publish.postType }}{% endif %}{% endset %}
|
|
113
|
-
{% call details({
|
|
114
|
-
classes: "rss-publish",
|
|
115
|
-
summary: publishSummary
|
|
116
|
-
}) %}
|
|
117
|
-
<form data-publish-feed="{{ feed.id }}">
|
|
118
|
-
{{ checkboxes({
|
|
119
|
-
idPrefix: "publish-enabled-" + feed.id,
|
|
120
|
-
name: "enabled",
|
|
121
|
-
items: [{
|
|
122
|
-
value: "true",
|
|
123
|
-
text: __("rss.publish.enabled"),
|
|
124
|
-
checked: feed.publish.enabled
|
|
125
|
-
}]
|
|
126
|
-
}) }}
|
|
127
|
-
|
|
128
|
-
{{ select({
|
|
129
|
-
id: "publish-post-type-" + feed.id,
|
|
130
|
-
name: "postType",
|
|
131
|
-
label: __("rss.publish.postType"),
|
|
132
|
-
items: typeItems
|
|
133
|
-
}) }}
|
|
134
|
-
|
|
135
|
-
{{ input({
|
|
136
|
-
id: "publish-content-" + feed.id,
|
|
137
|
-
name: "content",
|
|
138
|
-
label: __("rss.publish.content"),
|
|
139
|
-
hint: __("rss.publish.contentHint"),
|
|
140
|
-
value: feed.publish.content or "{{description}}"
|
|
141
|
-
}) }}
|
|
142
|
-
|
|
143
|
-
{{ input({
|
|
144
|
-
id: "publish-link-property-" + feed.id,
|
|
145
|
-
name: "linkProperty",
|
|
146
|
-
label: __("rss.publish.linkProperty"),
|
|
147
|
-
hint: __("rss.publish.linkPropertyHint"),
|
|
148
|
-
optional: true,
|
|
149
|
-
value: feed.publish.linkProperty
|
|
150
|
-
}) }}
|
|
151
|
-
|
|
152
|
-
{{ select({
|
|
153
|
-
id: "publish-status-" + feed.id,
|
|
154
|
-
name: "status",
|
|
155
|
-
label: __("rss.publish.status"),
|
|
156
|
-
items: [
|
|
157
|
-
{
|
|
158
|
-
text: __("rss.publish.draft"),
|
|
159
|
-
value: "draft",
|
|
160
|
-
selected: feed.publish.status != "published"
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
text: __("rss.publish.published"),
|
|
164
|
-
value: "published",
|
|
165
|
-
selected: feed.publish.status == "published"
|
|
166
|
-
}
|
|
167
|
-
]
|
|
168
|
-
}) }}
|
|
169
|
-
|
|
170
|
-
{{ button({
|
|
171
|
-
type: "submit",
|
|
172
|
-
text: __("rss.publish.save")
|
|
173
|
-
}) }}
|
|
174
|
-
</form>
|
|
175
|
-
{% endcall %}
|
|
176
203
|
<div class="rss-feed-actions">
|
|
177
204
|
<label class="rss-toggle">
|
|
178
205
|
<input
|
|
@@ -329,6 +356,38 @@
|
|
|
329
356
|
}
|
|
330
357
|
});
|
|
331
358
|
|
|
359
|
+
// Handle backfill
|
|
360
|
+
document.querySelectorAll('[data-backfill-feed]').forEach(form => {
|
|
361
|
+
form.addEventListener('submit', async (e) => {
|
|
362
|
+
e.preventDefault();
|
|
363
|
+
const feedId = e.target.dataset.backfillFeed;
|
|
364
|
+
const last = Number(new FormData(e.target).get('last'));
|
|
365
|
+
|
|
366
|
+
if (!last || last < 1) {
|
|
367
|
+
alert('Enter how many recent items to publish.');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const response = await fetch(`{{ mountPath }}/api/feeds/${feedId}/backfill`, {
|
|
373
|
+
method: 'POST',
|
|
374
|
+
headers: { 'Content-Type': 'application/json' },
|
|
375
|
+
body: JSON.stringify({ last })
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const body = await response.json();
|
|
379
|
+
if (response.ok) {
|
|
380
|
+
alert(body.message || 'Backfill queued.');
|
|
381
|
+
location.reload();
|
|
382
|
+
} else {
|
|
383
|
+
alert(body.error || 'Failed to queue backfill');
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
alert('Failed to queue backfill: ' + err.message);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
332
391
|
// Handle delete feed
|
|
333
392
|
document.querySelectorAll('[data-delete-feed]').forEach(btn => {
|
|
334
393
|
btn.addEventListener('click', async (e) => {
|
|
@@ -374,13 +433,24 @@
|
|
|
374
433
|
const response = await fetch(`{{ mountPath }}/api/feeds/${feedId}`, {
|
|
375
434
|
method: 'PATCH',
|
|
376
435
|
headers: { 'Content-Type': 'application/json' },
|
|
377
|
-
body: JSON.stringify({ publish })
|
|
436
|
+
body: JSON.stringify({ url: data.get('url'), publish })
|
|
378
437
|
});
|
|
379
438
|
|
|
439
|
+
const body = await response.json();
|
|
440
|
+
|
|
380
441
|
if (response.ok) {
|
|
442
|
+
// A feed can be correctly configured and still have nothing to
|
|
443
|
+
// publish: the watermark only lets through items dated after the
|
|
444
|
+
// moment publishing was switched on.
|
|
445
|
+
if (body.eligible === 0) {
|
|
446
|
+
alert(
|
|
447
|
+
'Saved. No items are waiting: only items published after you ' +
|
|
448
|
+
'enabled this will post. Use "Publish existing items" to ' +
|
|
449
|
+
'include the ones already cached.'
|
|
450
|
+
);
|
|
451
|
+
}
|
|
381
452
|
location.reload();
|
|
382
453
|
} else {
|
|
383
|
-
const body = await response.json();
|
|
384
454
|
alert(body.error || 'Failed to save publish settings');
|
|
385
455
|
}
|
|
386
456
|
} catch (err) {
|