mirror of
https://github.com/svemagie/indiekit-endpoint-microsub.git
synced 2026-04-02 15:35:00 +02:00
feat: Feed management with status tracking, edit, and rediscover
- Integrate updateFeedStatus into polling processor for health tracking - Add feed management UI showing status (active/error), errors, actions - Add edit feed URL feature to change non-RSS URLs to actual feeds - Add rediscover feature to run feed discovery and update URL - Add refresh button to force immediate poll - Update UI to use Indiekit's badge/button classes (badge--green/red, button--warning) - Add routes: /feeds/:feedId/edit, /feeds/:feedId/rediscover, /feeds/:feedId/refresh Fixes broken feeds by allowing users to: 1. Edit URL directly to the RSS/Atom feed 2. Click "Rediscover" to auto-find the feed from a blog URL 3. View error details and consecutive error counts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
84
views/feed-edit.njk
Normal file
84
views/feed-edit.njk
Normal file
@@ -0,0 +1,84 @@
|
||||
{% extends "layouts/reader.njk" %}
|
||||
|
||||
{% block reader %}
|
||||
<div class="settings">
|
||||
<a href="{{ baseUrl }}/channels/{{ channel.uid }}/feeds" class="back-link">
|
||||
{{ icon("previous") }} {{ __("microsub.feeds.title") }}
|
||||
</a>
|
||||
|
||||
<h2>{{ __("microsub.feeds.edit") }}</h2>
|
||||
|
||||
{% if error %}
|
||||
<div class="notice notice--error">
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="feed-edit">
|
||||
<div class="feed-edit__current">
|
||||
<h3>Current Feed</h3>
|
||||
<p class="feed-edit__url">{{ feed.url }}</p>
|
||||
{% if feed.title %}
|
||||
<p class="feed-edit__title">{{ feed.title }}</p>
|
||||
{% endif %}
|
||||
{% if feed.status == 'error' %}
|
||||
<div class="notice notice--error">
|
||||
<p><strong>Status:</strong> Error</p>
|
||||
{% if feed.lastError %}
|
||||
<p><strong>Last error:</strong> {{ feed.lastError }}</p>
|
||||
{% endif %}
|
||||
{% if feed.consecutiveErrors %}
|
||||
<p><strong>Consecutive errors:</strong> {{ feed.consecutiveErrors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/feeds/{{ feed._id }}/edit" class="feed-edit__form">
|
||||
{{ input({
|
||||
id: "url",
|
||||
name: "url",
|
||||
label: "New Feed URL",
|
||||
type: "url",
|
||||
required: true,
|
||||
value: feed.url,
|
||||
placeholder: "https://example.com/feed.xml",
|
||||
autocomplete: "off"
|
||||
}) }}
|
||||
|
||||
<p class="feed-edit__help">
|
||||
Enter the direct URL to the RSS, Atom, or JSON Feed. The URL will be validated before updating.
|
||||
</p>
|
||||
|
||||
<div class="button-group">
|
||||
{{ button({ text: "Update Feed URL" }) }}
|
||||
<a href="{{ baseUrl }}/channels/{{ channel.uid }}/feeds" class="button button--secondary">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="feed-edit__actions">
|
||||
<h3>Other Actions</h3>
|
||||
|
||||
<form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/feeds/{{ feed._id }}/rediscover" class="feed-edit__action">
|
||||
<p>Run feed discovery on the current URL to find the actual RSS/Atom feed.</p>
|
||||
{{ button({
|
||||
text: "Rediscover Feed",
|
||||
classes: "button--secondary"
|
||||
}) }}
|
||||
</form>
|
||||
|
||||
<form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/feeds/{{ feed._id }}/refresh" class="feed-edit__action">
|
||||
<p>Force refresh this feed now.</p>
|
||||
{{ button({
|
||||
text: "Refresh Now",
|
||||
classes: "button--secondary"
|
||||
}) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -13,7 +13,7 @@
|
||||
{% if feeds.length > 0 %}
|
||||
<div class="feeds__list">
|
||||
{% for feed in feeds %}
|
||||
<div class="feeds__item">
|
||||
<div class="feeds__item{% if feed.status == 'error' %} feeds__item--error{% endif %}">
|
||||
<div class="feeds__info">
|
||||
{% if feed.photo %}
|
||||
<img src="{{ feed.photo }}"
|
||||
@@ -25,19 +25,51 @@
|
||||
onerror="this.style.display='none'">
|
||||
{% endif %}
|
||||
<div class="feeds__details">
|
||||
<span class="feeds__name">{{ feed.title or feed.url }}</span>
|
||||
<span class="feeds__name">
|
||||
{{ feed.title or feed.url }}
|
||||
{% if feed.status == 'error' %}
|
||||
<span class="badge badge--red">Error</span>
|
||||
{% elif feed.status == 'active' %}
|
||||
<span class="badge badge--green">Active</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<a href="{{ feed.url }}" class="feeds__url" target="_blank" rel="noopener">
|
||||
{{ feed.url | replace("https://", "") | replace("http://", "") }}
|
||||
</a>
|
||||
{% if feed.lastError %}
|
||||
<span class="feeds__error">{{ feed.lastError }}</span>
|
||||
{% endif %}
|
||||
{% if feed.consecutiveErrors > 0 %}
|
||||
<span class="feeds__error-count">{{ feed.consecutiveErrors }} consecutive errors</span>
|
||||
{% endif %}
|
||||
{% if feed.lastSuccessAt %}
|
||||
<span class="feeds__meta">Last success: {{ feed.lastSuccessAt | date("relative") }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/feeds/remove" class="feeds__actions">
|
||||
<input type="hidden" name="url" value="{{ feed.url }}">
|
||||
{{ button({
|
||||
text: __("microsub.feeds.unfollow"),
|
||||
classes: "button--secondary button--small"
|
||||
}) }}
|
||||
</form>
|
||||
<div class="feeds__actions">
|
||||
<a href="{{ baseUrl }}/channels/{{ channel.uid }}/feeds/{{ feed._id }}/edit"
|
||||
class="button button--secondary button--small"
|
||||
title="Edit feed URL">
|
||||
{{ icon("edit") }}
|
||||
</a>
|
||||
<form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/feeds/{{ feed._id }}/rediscover" style="display:inline;">
|
||||
<button type="submit" class="button button--secondary button--small" title="Rediscover feed">
|
||||
{{ icon("discover") }}
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/feeds/{{ feed._id }}/refresh" style="display:inline;">
|
||||
<button type="submit" class="button button--secondary button--small" title="Refresh now">
|
||||
{{ icon("refresh") }}
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/feeds/remove" style="display:inline;">
|
||||
<input type="hidden" name="url" value="{{ feed.url }}">
|
||||
<button type="submit" class="button button--warning button--small" title="Unfollow">
|
||||
{{ icon("delete") }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -46,11 +46,11 @@
|
||||
<div class="search__feed">
|
||||
<span class="search__name">
|
||||
{{ result.title or "Feed" }}
|
||||
<span class="search__type badge badge--{% if result.valid %}info{% else %}warning{% endif %}">
|
||||
<span class="search__type badge badge--small{% if result.valid %} badge--green{% else %} badge--yellow{% endif %}">
|
||||
{{ result.typeLabel }}
|
||||
</span>
|
||||
{% if result.isCommentsFeed %}
|
||||
<span class="search__type badge badge--warning">Comments</span>
|
||||
<span class="search__type badge badge--small badge--yellow">Comments</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="search__url">{{ result.url | replace("https://", "") | replace("http://", "") }}</span>
|
||||
@@ -73,7 +73,7 @@
|
||||
}) }}
|
||||
</form>
|
||||
{% else %}
|
||||
<span class="search__invalid-badge">Invalid</span>
|
||||
<span class="badge badge--small badge--red">Invalid</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
Reference in New Issue
Block a user