diff --git a/lib/controllers/api.js b/lib/controllers/api.js index 3790814..984bb22 100644 --- a/lib/controllers/api.js +++ b/lib/controllers/api.js @@ -249,12 +249,14 @@ function sanitizeBlog(blog) { * @returns {object} Sanitized item */ function sanitizeItem(item) { + const published = item.published ? new Date(item.published) : null; return { id: item._id.toString(), url: item.url, title: item.title, summary: item.summary, published: item.published, + isFuture: published ? published > new Date() : false, author: item.author, photo: item.photo, categories: item.categories, diff --git a/lib/sync/feed.js b/lib/sync/feed.js index 69e8400..a6e4084 100644 --- a/lib/sync/feed.js +++ b/lib/sync/feed.js @@ -134,14 +134,14 @@ function parseJsonFeed(content, feedUrl, maxItems) { const items = (feed.items || []).slice(0, maxItems).map((item) => ({ uid: generateUid(feedUrl, item.id || item.url), url: item.url || item.external_url, - title: item.title || "Untitled", + title: decodeEntities(item.title) || "Untitled", content: { html: item.content_html ? sanitizeHtml(item.content_html, SANITIZE_OPTIONS) : undefined, text: item.content_text, }, - summary: item.summary || truncateText(item.content_text, 300), + summary: decodeEntities(item.summary) || truncateText(item.content_text, 300), published: item.date_published ? new Date(item.date_published) : new Date(), updated: item.date_modified ? new Date(item.date_modified) : undefined, author: item.author || (item.authors?.[0]), @@ -171,7 +171,7 @@ function normalizeItem(item, feedUrl) { return { uid: generateUid(feedUrl, item.guid || item.link), url: item.link || item.origlink, - title: item.title || "Untitled", + title: decodeEntities(item.title) || "Untitled", content: { html: description ? sanitizeHtml(description, SANITIZE_OPTIONS) : undefined, text: stripHtml(description), @@ -200,16 +200,37 @@ function generateUid(feedUrl, itemId) { } /** - * Strip HTML tags from string + * Strip HTML tags and decode HTML entities from string * @param {string} html - HTML string * @returns {string} Plain text */ function stripHtml(html) { if (!html) return ""; - return html - .replace(/<[^>]*>/g, " ") - .replace(/\s+/g, " ") - .trim(); + return decodeEntities( + html + .replace(/<[^>]*>/g, " ") + .replace(/\s+/g, " ") + .trim() + ); +} + +/** + * Decode HTML entities to their character equivalents + * @param {string} str - String with HTML entities + * @returns {string} Decoded string + */ +function decodeEntities(str) { + if (!str) return ""; + return str + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'") + .replace(/'/g, "'") + .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code))) + .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))); } /** diff --git a/package.json b/package.json index a86954d..7e5e800 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-blogroll", - "version": "1.0.11", + "version": "1.0.12", "description": "Blogroll endpoint for Indiekit. Aggregates blog feeds from OPML, JSON feeds, or manual entry.", "keywords": [ "indiekit",