feat: category tabs, future post badge, HTML entity decoding
- Decode HTML entities (& ' etc) in feed titles and summaries - Add isFuture flag to API items for future-dated posts - Bump version to 1.0.12 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -249,12 +249,14 @@ function sanitizeBlog(blog) {
|
|||||||
* @returns {object} Sanitized item
|
* @returns {object} Sanitized item
|
||||||
*/
|
*/
|
||||||
function sanitizeItem(item) {
|
function sanitizeItem(item) {
|
||||||
|
const published = item.published ? new Date(item.published) : null;
|
||||||
return {
|
return {
|
||||||
id: item._id.toString(),
|
id: item._id.toString(),
|
||||||
url: item.url,
|
url: item.url,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
summary: item.summary,
|
summary: item.summary,
|
||||||
published: item.published,
|
published: item.published,
|
||||||
|
isFuture: published ? published > new Date() : false,
|
||||||
author: item.author,
|
author: item.author,
|
||||||
photo: item.photo,
|
photo: item.photo,
|
||||||
categories: item.categories,
|
categories: item.categories,
|
||||||
|
|||||||
+27
-6
@@ -134,14 +134,14 @@ function parseJsonFeed(content, feedUrl, maxItems) {
|
|||||||
const items = (feed.items || []).slice(0, maxItems).map((item) => ({
|
const items = (feed.items || []).slice(0, maxItems).map((item) => ({
|
||||||
uid: generateUid(feedUrl, item.id || item.url),
|
uid: generateUid(feedUrl, item.id || item.url),
|
||||||
url: item.url || item.external_url,
|
url: item.url || item.external_url,
|
||||||
title: item.title || "Untitled",
|
title: decodeEntities(item.title) || "Untitled",
|
||||||
content: {
|
content: {
|
||||||
html: item.content_html
|
html: item.content_html
|
||||||
? sanitizeHtml(item.content_html, SANITIZE_OPTIONS)
|
? sanitizeHtml(item.content_html, SANITIZE_OPTIONS)
|
||||||
: undefined,
|
: undefined,
|
||||||
text: item.content_text,
|
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(),
|
published: item.date_published ? new Date(item.date_published) : new Date(),
|
||||||
updated: item.date_modified ? new Date(item.date_modified) : undefined,
|
updated: item.date_modified ? new Date(item.date_modified) : undefined,
|
||||||
author: item.author || (item.authors?.[0]),
|
author: item.author || (item.authors?.[0]),
|
||||||
@@ -171,7 +171,7 @@ function normalizeItem(item, feedUrl) {
|
|||||||
return {
|
return {
|
||||||
uid: generateUid(feedUrl, item.guid || item.link),
|
uid: generateUid(feedUrl, item.guid || item.link),
|
||||||
url: item.link || item.origlink,
|
url: item.link || item.origlink,
|
||||||
title: item.title || "Untitled",
|
title: decodeEntities(item.title) || "Untitled",
|
||||||
content: {
|
content: {
|
||||||
html: description ? sanitizeHtml(description, SANITIZE_OPTIONS) : undefined,
|
html: description ? sanitizeHtml(description, SANITIZE_OPTIONS) : undefined,
|
||||||
text: stripHtml(description),
|
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
|
* @param {string} html - HTML string
|
||||||
* @returns {string} Plain text
|
* @returns {string} Plain text
|
||||||
*/
|
*/
|
||||||
function stripHtml(html) {
|
function stripHtml(html) {
|
||||||
if (!html) return "";
|
if (!html) return "";
|
||||||
return html
|
return decodeEntities(
|
||||||
|
html
|
||||||
.replace(/<[^>]*>/g, " ")
|
.replace(/<[^>]*>/g, " ")
|
||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
.trim();
|
.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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-blogroll",
|
"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.",
|
"description": "Blogroll endpoint for Indiekit. Aggregates blog feeds from OPML, JSON feeds, or manual entry.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"indiekit",
|
"indiekit",
|
||||||
|
|||||||
Reference in New Issue
Block a user