1
0
Fork 0
mirror of https://codeberg.org/Reuh/feather.git synced 2025-10-27 18:19:32 +00:00

feat: allow marking items as unread by restoring their files and add option to not write read articles even when synced

This commit is contained in:
Étienne Fildadut 2025-10-17 13:59:00 +02:00
parent 48c2c0f850
commit b5474cb376
4 changed files with 33 additions and 17 deletions

View file

@ -82,13 +82,16 @@ class FeatherApp:
to_mark_as_read = [] to_mark_as_read = []
to_mark_as_unread = [] to_mark_as_unread = []
for article in self.iter_articles(): for article in self.iter_articles():
if not article.has_html(): has_html = article.has_html()
if article.unread: if article.unread and not has_html:
to_mark_as_read.append(article) to_mark_as_read.append(article)
marked_as_read += 1 marked_as_read += 1
else: elif not article.unread and (
to_mark_as_unread.append(article) (config.write_read_articles and not has_html)
marked_as_unread += 1 or (not config.write_read_articles and has_html)
):
to_mark_as_unread.append(article)
marked_as_unread += 1
if len(to_mark_as_read) == len(to_mark_as_unread) == 0: if len(to_mark_as_read) == len(to_mark_as_unread) == 0:
return # nothing to do return # nothing to do
@ -260,6 +263,7 @@ class FeatherApp:
"""Regenerate all local files using local data only""" """Regenerate all local files using local data only"""
for article in self.iter_articles(): for article in self.iter_articles():
article.regenerate() article.regenerate()
self.remove_empty_categories()
def clear_data(self): def clear_data(self):
"""Delete all local data""" """Delete all local data"""

View file

@ -23,7 +23,7 @@ password = "password"
# Set to 0 to let Feather choose (200 for ttrss, 1000 for googlereader). # Set to 0 to let Feather choose (200 for ttrss, 1000 for googlereader).
# Can be set through the environment variable SERVER_ARTICLES_PER_REQUEST. # Can be set through the environment variable SERVER_ARTICLES_PER_REQUEST.
articles_per_request = 0 articles_per_request = 0
# Set to true to only sync unread articles; Feather will not retrieve or store any read article. # Set to true to only sync unread articles; Feather will not retrieve any read article from the server.
# Can be set through the environment variable SERVER_ONLY_SYNC_UNREAD_ARTICLES. # Can be set through the environment variable SERVER_ONLY_SYNC_UNREAD_ARTICLES.
only_sync_unread_articles = true only_sync_unread_articles = true
@ -36,6 +36,13 @@ data = "data"
reader = "reader" reader = "reader"
[html] [html]
# If set to true, Feather will also generate articles files for read articles.
# Also remember to set server.only_sync_unread_articles = false; otherwise this will do nothing.
# The the mark-as-unread behavior will also change depending on this value:
# - if false, marking an article as unread requires its file to be recreated/restored from the trash;
# - if true, marking an article as unread requires deleting its article file (same as mark-as-read).
# Can be set through the environment variable HTML_WRITE_READ_ARTICLES.
write_read_articles = false
# Template used for generating article HTML files. All templates are Jinja2 templates. # Template used for generating article HTML files. All templates are Jinja2 templates.
# Available fields: # Available fields:
# - id: article id (int | str) # - id: article id (int | str)

View file

@ -65,6 +65,7 @@ class Config:
self.timezone: ZoneInfo = ZoneInfo(str(get_config("datetime", "timezone"))) self.timezone: ZoneInfo = ZoneInfo(str(get_config("datetime", "timezone")))
self.time_format: str = str(get_config("datetime", "format")) self.time_format: str = str(get_config("datetime", "format"))
self.write_read_articles: bool = bool(get_config("html", "write_read_articles"))
self.article_template: Template = Template( self.article_template: Template = Template(
str(get_config("html", "article_template")), autoescape=True str(get_config("html", "article_template")), autoescape=True
) )

View file

@ -234,8 +234,11 @@ class Article(ABC):
def _delete_html(self, missing_ok=False): def _delete_html(self, missing_ok=False):
"""Delete the HTML file associated with this article.""" """Delete the HTML file associated with this article."""
# Delete a HTML file for a JSON object # Delete a HTML file for a JSON object
html_path = self.config.html_root / self.html_path if self.html_path is None:
html_path.unlink(missing_ok=missing_ok) return
else:
html_path = self.config.html_root / self.html_path
html_path.unlink(missing_ok=missing_ok)
def has_html(self) -> bool: def has_html(self) -> bool:
"""Check if the HTML file associated with the article exists on disk.""" """Check if the HTML file associated with the article exists on disk."""
@ -251,13 +254,14 @@ class Article(ABC):
def write(self, recompute_paths=False): def write(self, recompute_paths=False):
"""Write all the files associated with this article to disk.""" """Write all the files associated with this article to disk."""
try: if self.unread or self.config.write_read_articles:
self._write_html(recompute_path=recompute_paths) try:
except FileExistsError: self._write_html(recompute_path=recompute_paths)
raise except FileExistsError:
except: raise
self._delete_html(missing_ok=True) except:
raise self._delete_html(missing_ok=True)
raise
try: try:
self._write_json(recompute_path=recompute_paths) self._write_json(recompute_path=recompute_paths)
except FileExistsError: except FileExistsError: