reader 3.15__tar.gz → 3.17__tar.gz
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.
- {reader-3.15 → reader-3.17}/CHANGES.rst +84 -0
- {reader-3.15 → reader-3.17}/MANIFEST.in +1 -0
- {reader-3.15/src/reader.egg-info → reader-3.17}/PKG-INFO +4 -2
- reader-3.17/docs/.DS_Store +0 -0
- {reader-3.15 → reader-3.17}/docs/api.rst +10 -0
- {reader-3.15 → reader-3.17}/docs/app.rst +25 -0
- {reader-3.15 → reader-3.17}/docs/dev.rst +28 -6
- reader-3.17/docs/screenshots/entries-v2-dark.png +0 -0
- reader-3.17/docs/screenshots/entries-v2-filters-light.png +0 -0
- {reader-3.15 → reader-3.17}/pyproject.toml +3 -0
- {reader-3.15 → reader-3.17}/src/reader/__init__.py +2 -1
- {reader-3.15 → reader-3.17}/src/reader/_app/__init__.py +20 -19
- {reader-3.15 → reader-3.17}/src/reader/_app/templates/entries.html +4 -1
- {reader-3.15 → reader-3.17}/src/reader/_app/templates/entry.html +1 -1
- {reader-3.15 → reader-3.17}/src/reader/_app/templates/layout.html +1 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/templates/macros.html +2 -8
- reader-3.17/src/reader/_app/v2/__init__.py +123 -0
- reader-3.17/src/reader/_app/v2/forms.py +159 -0
- reader-3.17/src/reader/_app/v2/static/style.css +27 -0
- reader-3.17/src/reader/_app/v2/static/theme.js +91 -0
- reader-3.17/src/reader/_app/v2/templates/v2/entries.html +232 -0
- reader-3.17/src/reader/_app/v2/templates/v2/layout.html +116 -0
- reader-3.17/src/reader/_app/v2/templates/v2/macros.html +47 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/feedparser.py +22 -2
- {reader-3.15 → reader-3.17}/src/reader/_plugins/cli_status.py +25 -14
- {reader-3.15 → reader-3.17}/src/reader/_storage/_base.py +6 -10
- {reader-3.15 → reader-3.17}/src/reader/_storage/_entries.py +34 -3
- {reader-3.15 → reader-3.17}/src/reader/_storage/_schema.py +8 -1
- {reader-3.15 → reader-3.17}/src/reader/_storage/_search.py +38 -13
- {reader-3.15 → reader-3.17}/src/reader/_storage/_sqlite_utils.py +13 -2
- {reader-3.15 → reader-3.17}/src/reader/_types.py +58 -0
- {reader-3.15 → reader-3.17}/src/reader/_update.py +1 -0
- {reader-3.15 → reader-3.17}/src/reader/_utils.py +7 -0
- {reader-3.15 → reader-3.17}/src/reader/core.py +106 -12
- {reader-3.15 → reader-3.17}/src/reader/types.py +75 -1
- reader-3.17/src/reader/utils.py +78 -0
- {reader-3.15 → reader-3.17/src/reader.egg-info}/PKG-INFO +4 -2
- {reader-3.15 → reader-3.17}/src/reader.egg-info/SOURCES.txt +13 -1
- {reader-3.15 → reader-3.17}/src/reader.egg-info/requires.txt +2 -0
- {reader-3.15 → reader-3.17}/tests/conftest.py +15 -1
- {reader-3.15 → reader-3.17}/tests/data/full.atom +38 -0
- {reader-3.15 → reader-3.17}/tests/data/full.atom.py +31 -0
- {reader-3.15 → reader-3.17}/tests/data/full.rss +14 -0
- {reader-3.15 → reader-3.17}/tests/data/full.rss.py +16 -0
- {reader-3.15 → reader-3.17}/tests/data/relative.rss +1 -1
- {reader-3.15 → reader-3.17}/tests/data/relative.rss.py +1 -1
- {reader-3.15 → reader-3.17}/tests/fakeparser.py +7 -31
- {reader-3.15 → reader-3.17}/tests/test__types.py +5 -2
- {reader-3.15 → reader-3.17}/tests/test_app.py +11 -29
- {reader-3.15 → reader-3.17}/tests/test_changes.py +15 -17
- {reader-3.15 → reader-3.17}/tests/test_cli.py +9 -8
- {reader-3.15 → reader-3.17}/tests/test_plugins_cli_status.py +36 -27
- {reader-3.15 → reader-3.17}/tests/test_plugins_enclosure_dedupe.py +1 -3
- {reader-3.15 → reader-3.17}/tests/test_plugins_entry_dedupe.py +14 -21
- {reader-3.15 → reader-3.17}/tests/test_plugins_mark_as_read.py +9 -21
- {reader-3.15 → reader-3.17}/tests/test_plugins_readtime.py +4 -13
- {reader-3.15 → reader-3.17}/tests/test_reader.py +194 -192
- {reader-3.15 → reader-3.17}/tests/test_reader_context.py +1 -3
- {reader-3.15 → reader-3.17}/tests/test_reader_filter.py +5 -1
- {reader-3.15 → reader-3.17}/tests/test_reader_hooks.py +3 -9
- {reader-3.15 → reader-3.17}/tests/test_reader_integration.py +5 -5
- {reader-3.15 → reader-3.17}/tests/test_reader_private.py +11 -13
- {reader-3.15 → reader-3.17}/tests/test_reader_search.py +104 -45
- {reader-3.15 → reader-3.17}/tests/test_reader_sort.py +8 -10
- {reader-3.15 → reader-3.17}/tests/test_reader_update.py +17 -25
- reader-3.17/tests/test_reader_utils.py +57 -0
- {reader-3.15 → reader-3.17}/tests/test_storage.py +7 -16
- {reader-3.15 → reader-3.17}/tests/test_tags.py +5 -10
- {reader-3.15 → reader-3.17}/tests/test_types.py +31 -0
- {reader-3.15 → reader-3.17}/tests/utils.py +34 -0
- {reader-3.15 → reader-3.17}/tox.ini +3 -3
- {reader-3.15 → reader-3.17}/LICENSE +0 -0
- {reader-3.15 → reader-3.17}/README.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/Makefile +0 -0
- {reader-3.15 → reader-3.17}/docs/changelog.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/cli.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/conf.py +0 -0
- {reader-3.15 → reader-3.17}/docs/config.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/contributing.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/dev-app.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/guide.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/images/redesign-01.png +0 -0
- {reader-3.15 → reader-3.17}/docs/index.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/install.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/internal.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/make.bat +0 -0
- {reader-3.15 → reader-3.17}/docs/plugins.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/screenshots/dillo.png +0 -0
- {reader-3.15 → reader-3.17}/docs/screenshots/entries-feed.png +0 -0
- {reader-3.15 → reader-3.17}/docs/screenshots/entries.png +0 -0
- {reader-3.15 → reader-3.17}/docs/screenshots/entry-one.png +0 -0
- {reader-3.15 → reader-3.17}/docs/screenshots/entry-two.png +0 -0
- {reader-3.15 → reader-3.17}/docs/screenshots/feeds.png +0 -0
- {reader-3.15 → reader-3.17}/docs/screenshots/lynx.png +0 -0
- {reader-3.15 → reader-3.17}/docs/screenshots/search.png +0 -0
- {reader-3.15 → reader-3.17}/docs/tutorial.rst +0 -0
- {reader-3.15 → reader-3.17}/docs/why.rst +0 -0
- {reader-3.15 → reader-3.17}/examples/config.yaml +0 -0
- {reader-3.15 → reader-3.17}/examples/custom_headers.py +0 -0
- {reader-3.15 → reader-3.17}/examples/feed_slugs.py +0 -0
- {reader-3.15 → reader-3.17}/examples/parser_only.py +0 -0
- {reader-3.15 → reader-3.17}/examples/podcast.py +0 -0
- {reader-3.15 → reader-3.17}/examples/terminal.py +0 -0
- {reader-3.15 → reader-3.17}/run.sh +0 -0
- {reader-3.15 → reader-3.17}/scripts/.coverage +0 -0
- {reader-3.15 → reader-3.17}/scripts/backup.sh +0 -0
- {reader-3.15 → reader-3.17}/scripts/bench.py +0 -0
- {reader-3.15 → reader-3.17}/scripts/debug_storage_stats.py +0 -0
- {reader-3.15 → reader-3.17}/scripts/generate_import_all.py +0 -0
- {reader-3.15 → reader-3.17}/scripts/jscontrols.html +0 -0
- {reader-3.15 → reader-3.17}/scripts/jscontrols.py +0 -0
- {reader-3.15 → reader-3.17}/scripts/lines.sh +0 -0
- {reader-3.15 → reader-3.17}/scripts/release.py +0 -0
- {reader-3.15 → reader-3.17}/setup.cfg +0 -0
- {reader-3.15 → reader-3.17}/src/reader/__main__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/api_thing.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/cli.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/static/controls.js +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/static/style.css +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/templates/add_entry.html +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/templates/feeds.html +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/templates/metadata.html +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/templates/tags.html +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_app/wsgi.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_cli.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_config.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_hash_utils.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/_http_utils.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/_lazy.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/_url_utils.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/file.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/http.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/jsonfeed.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/requests/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_parser/requests/_lazy.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_plugins/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_plugins/enclosure_tags.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_plugins/preview_feed_list.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_plugins/share.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_plugins/sqlite_releases.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_plugins/templates/preview_feed_list.html +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_plugins/timer.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_storage/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_storage/_changes.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_storage/_feeds.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_storage/_html_utils.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_storage/_sql_utils.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_storage/_tags.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/api.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/encodings.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/exceptions.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/html.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/http.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/mixin.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/sgml.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/urls.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/_vendor/feedparser/util.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/exceptions.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/plugins/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/plugins/enclosure_dedupe.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/plugins/entry_dedupe.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/plugins/mark_as_read.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/plugins/readtime.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/plugins/ua_fallback.py +0 -0
- {reader-3.15 → reader-3.17}/src/reader/py.typed +0 -0
- {reader-3.15 → reader-3.17}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.15 → reader-3.17}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.15 → reader-3.17}/tests/data/10.json +0 -0
- {reader-3.15 → reader-3.17}/tests/data/10.json.py +0 -0
- {reader-3.15 → reader-3.17}/tests/data/custom +0 -0
- {reader-3.15 → reader-3.17}/tests/data/empty.atom +0 -0
- {reader-3.15 → reader-3.17}/tests/data/empty.atom.py +0 -0
- {reader-3.15 → reader-3.17}/tests/data/empty.json +0 -0
- {reader-3.15 → reader-3.17}/tests/data/empty.json.py +0 -0
- {reader-3.15 → reader-3.17}/tests/data/empty.rss +0 -0
- {reader-3.15 → reader-3.17}/tests/data/empty.rss.py +0 -0
- {reader-3.15 → reader-3.17}/tests/data/full.json +0 -0
- {reader-3.15 → reader-3.17}/tests/data/full.json.py +0 -0
- {reader-3.15 → reader-3.17}/tests/data/invalid.json +0 -0
- {reader-3.15 → reader-3.17}/tests/data/invalid.json.py +0 -0
- {reader-3.15 → reader-3.17}/tests/data/relative.atom +0 -0
- {reader-3.15 → reader-3.17}/tests/data/relative.atom.py +0 -0
- {reader-3.15 → reader-3.17}/tests/data/sqlite_releases.html +0 -0
- {reader-3.15 → reader-3.17}/tests/data/unknown.json +0 -0
- {reader-3.15 → reader-3.17}/tests/data/unknown.json.py +0 -0
- {reader-3.15 → reader-3.17}/tests/reader_methods.py +0 -0
- {reader-3.15 → reader-3.17}/tests/reader_test_plugins/__init__.py +0 -0
- {reader-3.15 → reader-3.17}/tests/reader_test_plugins/good.py +0 -0
- {reader-3.15 → reader-3.17}/tests/reader_test_plugins/init_error.py +0 -0
- {reader-3.15 → reader-3.17}/tests/reader_test_plugins/missing_dependency.py +0 -0
- {reader-3.15 → reader-3.17}/tests/reader_test_plugins/missing_entry_point.py +0 -0
- /reader-3.15/tests/test_utils.py → /reader-3.17/tests/test__utils.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_app_wsgi.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_bench.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_config.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_exceptions.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_hash_utils.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_html_utils.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_lazy_imports.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_parser.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_plugins_preview_feed_list.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_plugins_sqlite_releases.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_plugins_ua_fallback.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_reader_counts.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_reader_deprecations.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_reader_plugins.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_search.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_sql_utils.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_sqlite_utils.py +0 -0
- {reader-3.15 → reader-3.17}/tests/test_test_utils.py +0 -0
|
@@ -6,6 +6,90 @@ Changelog
|
|
|
6
6
|
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
Version 3.17
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2025-03-08
|
|
13
|
+
|
|
14
|
+
.. attention::
|
|
15
|
+
|
|
16
|
+
This is the last release to support Python 3.10;
|
|
17
|
+
see :issue:`354` for details.
|
|
18
|
+
|
|
19
|
+
* Support PyPy 3.11. (:issue:`354`)
|
|
20
|
+
|
|
21
|
+
* Start working on a web app :ref:`re-design <app roadmap>`
|
|
22
|
+
based on `htmx`_ and `Bootstrap`_,
|
|
23
|
+
beginning with a new version of the main entries page,
|
|
24
|
+
and dark mode support;
|
|
25
|
+
some :ref:`screenshots <app screenshots>`.
|
|
26
|
+
(:issue:`318`)
|
|
27
|
+
|
|
28
|
+
* :attr:`Entry.feed_resolved_title`:
|
|
29
|
+
return both the source and feed titles only if they are different.
|
|
30
|
+
|
|
31
|
+
* The :mod:`~reader._plugins.cli_status` plugin
|
|
32
|
+
now creates only one entry per command,
|
|
33
|
+
with the newest run first.
|
|
34
|
+
|
|
35
|
+
.. _htmx: https://htmx.org/
|
|
36
|
+
.. _Bootstrap: https://getbootstrap.com/
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
Version 3.16
|
|
40
|
+
------------
|
|
41
|
+
|
|
42
|
+
Released 2024-12-08
|
|
43
|
+
|
|
44
|
+
* Parse and store :attr:`Entry.source` for Atom and RSS feeds. (:issue:`267`)
|
|
45
|
+
|
|
46
|
+
* Add :attr:`Entry.feed_resolved_title` and :attr:`Feed.resolved_title` properties.
|
|
47
|
+
* The ``feed`` search column now indexes :attr:`Entry.feed_resolved_title`,
|
|
48
|
+
instead of feed :attr:`~Feed.user_title` or :attr:`~Feed.title`.
|
|
49
|
+
* Allow filtering entries by the entry source.
|
|
50
|
+
|
|
51
|
+
* Add :func:`reader.utils.archive_entries` for
|
|
52
|
+
copying entries to an "archived" feed.
|
|
53
|
+
(:issue:`290`)
|
|
54
|
+
|
|
55
|
+
* Add :meth:`~Reader.copy_entry`.
|
|
56
|
+
* Allow archiving entries from the web app.
|
|
57
|
+
|
|
58
|
+
* Fix bug causing :class:`Reader` operations
|
|
59
|
+
from a thread other than the one that created the instance
|
|
60
|
+
to happen with foreign key constraint enforcement disabled
|
|
61
|
+
(e.g. deleting a feed from another thread would not delete its entries).
|
|
62
|
+
|
|
63
|
+
This bug exists since using :class:`Reader` instances from other threads
|
|
64
|
+
became allowed in `2.15 <Version 2.15_>`_.
|
|
65
|
+
|
|
66
|
+
Serving the web application with ``python -m reader serve``
|
|
67
|
+
is known to be affected.
|
|
68
|
+
Serving it with uWSGI without threads (the default)
|
|
69
|
+
should not be affected.
|
|
70
|
+
|
|
71
|
+
.. attention::
|
|
72
|
+
|
|
73
|
+
**Your database may be in an inconsistent state because of this bug.**
|
|
74
|
+
|
|
75
|
+
It is recommended you run `PRAGMA foreign_key_check`_ on your database.
|
|
76
|
+
|
|
77
|
+
If you are upgrading from a version prior to 3.16
|
|
78
|
+
(i.e. were not using a pre-release version of *reader*),
|
|
79
|
+
the migration will do so for you.
|
|
80
|
+
If there are inconsistencies, you will get this error::
|
|
81
|
+
|
|
82
|
+
StorageError: integrity error: after migrating to version 43:
|
|
83
|
+
integrity error: FOREIGN KEY constraint failed
|
|
84
|
+
|
|
85
|
+
* Fix :meth:`~Reader.enable_search` / :meth:`~Reader.update_search`
|
|
86
|
+
not working when the search database is missing but change tracking is enabled
|
|
87
|
+
(e.g. when restoring the main database from backup).
|
|
88
|
+
(:issue:`362`)
|
|
89
|
+
|
|
90
|
+
.. _PRAGMA foreign_key_check: https://www.sqlite.org/pragma.html#pragma_foreign_key_check
|
|
91
|
+
|
|
92
|
+
|
|
9
93
|
Version 3.15
|
|
10
94
|
------------
|
|
11
95
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.17
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -50,6 +50,8 @@ Provides-Extra: app
|
|
|
50
50
|
Requires-Dist: flask>=0.10; extra == "app"
|
|
51
51
|
Requires-Dist: humanize!=4.7.*,>=4; extra == "app"
|
|
52
52
|
Requires-Dist: PyYAML; extra == "app"
|
|
53
|
+
Requires-Dist: WTForms; extra == "app"
|
|
54
|
+
Requires-Dist: jinja2-fragments; extra == "app"
|
|
53
55
|
Provides-Extra: unstable-plugins
|
|
54
56
|
Requires-Dist: requests; extra == "unstable-plugins"
|
|
55
57
|
Requires-Dist: mutagen; extra == "unstable-plugins"
|
|
Binary file
|
|
@@ -40,6 +40,9 @@ Data objects
|
|
|
40
40
|
.. autoclass:: Enclosure
|
|
41
41
|
:members:
|
|
42
42
|
|
|
43
|
+
.. autoclass:: EntrySource
|
|
44
|
+
:members:
|
|
45
|
+
|
|
43
46
|
.. autoclass:: EntrySearchResult
|
|
44
47
|
:members:
|
|
45
48
|
|
|
@@ -187,3 +190,10 @@ Constants
|
|
|
187
190
|
.. autodata:: reader.core.DEFAULT_RESERVED_NAME_SCHEME
|
|
188
191
|
|
|
189
192
|
.. autodata:: reader.plugins.DEFAULT_PLUGINS
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
Utilities
|
|
197
|
+
---------
|
|
198
|
+
|
|
199
|
+
.. autofunction:: reader.utils.archive_entries
|
|
@@ -67,9 +67,34 @@ If running on a personal computer, you can use cron to run ``serve`` at boot::
|
|
|
67
67
|
.. _Werkzeug's development server: http://werkzeug.pocoo.org/docs/0.14/serving/#werkzeug.serving.run_simple
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
.. _app screenshots:
|
|
71
|
+
|
|
70
72
|
Screenshots
|
|
71
73
|
-----------
|
|
72
74
|
|
|
75
|
+
Following are screenshots of the web app re-design
|
|
76
|
+
mentioned in the :ref:`roadmap <app roadmap>`.
|
|
77
|
+
For the legacy web app, see `Screenshots (legacy)`_.
|
|
78
|
+
|
|
79
|
+
Main page
|
|
80
|
+
~~~~~~~~~
|
|
81
|
+
|
|
82
|
+
.. figure:: screenshots/entries-v2-dark.png
|
|
83
|
+
:width: 240px
|
|
84
|
+
|
|
85
|
+
main page (dark mode)
|
|
86
|
+
|
|
87
|
+
.. figure:: screenshots/entries-v2-filters-light.png
|
|
88
|
+
:width: 240px
|
|
89
|
+
|
|
90
|
+
main page – more filters (light mode)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
Screenshots (legacy)
|
|
94
|
+
--------------------
|
|
95
|
+
|
|
96
|
+
Following are screenshots of the original (legacy) web app.
|
|
97
|
+
|
|
73
98
|
Main page
|
|
74
99
|
~~~~~~~~~
|
|
75
100
|
|
|
@@ -34,6 +34,9 @@ but I will prioritize supporting :doc:`contributors <contributing>`
|
|
|
34
34
|
|
|
35
35
|
* OPML support, :issue:`165`
|
|
36
36
|
* :ref:`deleting entries <deleting entries>`
|
|
37
|
+
|
|
38
|
+
* archiving important entries of deleted feeds, :issue:`290`
|
|
39
|
+
|
|
37
40
|
* :ref:`feed interaction statistics <counts api>`
|
|
38
41
|
|
|
39
42
|
* security
|
|
@@ -69,7 +72,6 @@ but I will prioritize supporting :doc:`contributors <contributing>`
|
|
|
69
72
|
|
|
70
73
|
* add more fields to data objects
|
|
71
74
|
|
|
72
|
-
* entry source, :issue:`276`
|
|
73
75
|
* extra data, as an escape hatch, :issue:`277`
|
|
74
76
|
|
|
75
77
|
* :ref:`multiple storage implementations <multiple storage implementations>`
|
|
@@ -120,8 +122,14 @@ Long term, I'd like to:
|
|
|
120
122
|
* switch to `htmx`_ instead of using a home-grown solution
|
|
121
123
|
* spin it off into a separate package/project
|
|
122
124
|
|
|
123
|
-
|
|
125
|
+
**2025 update**:
|
|
126
|
+
I've started working on a re-design based on `htmx`_ and `Bootstrap`_;
|
|
127
|
+
some :ref:`screenshots <app screenshots>`.
|
|
128
|
+
The new app will be available in parallel with the old one
|
|
129
|
+
until it reaches feature parity.
|
|
124
130
|
|
|
131
|
+
.. _htmx: https://htmx.org/
|
|
132
|
+
.. _Bootstrap: https://getbootstrap.com/
|
|
125
133
|
|
|
126
134
|
|
|
127
135
|
.. _compat:
|
|
@@ -360,6 +368,8 @@ Speeding up ``get_entry_counts(feed=...)``:
|
|
|
360
368
|
* having an index on entries(feed) yielded a 4x improvement: :issue:`251`
|
|
361
369
|
* even better, we should cache commonly-used counts: :issue:`306#issuecomment-1694655504`
|
|
362
370
|
|
|
371
|
+
Official guidance: https://www.sqlite.org/queryplanner-ng.html#howtofix
|
|
372
|
+
|
|
363
373
|
|
|
364
374
|
Parser
|
|
365
375
|
~~~~~~
|
|
@@ -512,6 +522,16 @@ From the initial issue:
|
|
|
512
522
|
* use cases: :issue:`149#issuecomment-700066794`
|
|
513
523
|
* initial requirements: :issue:`149#issuecomment-700532183`
|
|
514
524
|
|
|
525
|
+
Splitting a feed into two feeds is discussed in :issue:`221`.
|
|
526
|
+
The main takeaway at the time (2021) was that
|
|
527
|
+
not all entry data can be retrieved through the storage API,
|
|
528
|
+
so it's not fully possible to round-trip an entry.
|
|
529
|
+
Based on a quick 2024 survey,
|
|
530
|
+
the only affected attributes
|
|
531
|
+
are :attr:`~.EntryUpdateIntent.feed_order` (can't get; not critical)
|
|
532
|
+
and :attr:`~Entry.original_feed_url` (can't set; will be required for
|
|
533
|
+
archiving important entries of deleted feeds, :issue:`290`).
|
|
534
|
+
|
|
515
535
|
|
|
516
536
|
Resource tags / metadata
|
|
517
537
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
@@ -651,6 +671,8 @@ Why I want to postpone batch update/set methods:
|
|
|
651
671
|
tl:dr: Performance is likely a non-issue with SQLite,
|
|
652
672
|
convenience can be added on top as a plugin.
|
|
653
673
|
|
|
674
|
+
(2025) Why it may be worth adding batch interfaces anyway (even if underneath the storage implementation doesn't actually batch) – it allows for future optimization: https://blog.glyph.im/2022/12/potato-programming.html
|
|
675
|
+
|
|
654
676
|
See the 2.12 reader._app.ResourceTags class for an idea of how to
|
|
655
677
|
represent a bunch of tags in a reserved-name-scheme-agnostic way
|
|
656
678
|
(useful e.g. for when get_entries() should return tags x, y, z of each entry).
|
|
@@ -708,16 +730,16 @@ Wrapping underlying storage exceptions
|
|
|
708
730
|
|
|
709
731
|
Which exception to wrap, and which not: :issue:`21#issuecomment-365442439`.
|
|
710
732
|
|
|
711
|
-
In version 3.10 (November 2023), all internal APIs were changed
|
|
712
|
-
to use timezone-aware datetimes, with the timezone set to UTC,
|
|
713
|
-
in preparation for support for any timezone.
|
|
714
|
-
|
|
715
733
|
|
|
716
734
|
Timezone handling
|
|
717
735
|
~~~~~~~~~~~~~~~~~
|
|
718
736
|
|
|
719
737
|
Aware vs. naive, and what's needed to go fully aware: :issue:`233#issuecomment-881618002`.
|
|
720
738
|
|
|
739
|
+
In version 3.10 (November 2023), all internal APIs were changed
|
|
740
|
+
to use timezone-aware datetimes, with the timezone set to UTC,
|
|
741
|
+
in preparation for support for any timezone.
|
|
742
|
+
|
|
721
743
|
|
|
722
744
|
OPML support
|
|
723
745
|
~~~~~~~~~~~~
|
|
Binary file
|
|
Binary file
|
|
@@ -37,7 +37,7 @@ Print the titles of the unread entries::
|
|
|
37
37
|
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
__version__ = '3.
|
|
40
|
+
__version__ = '3.17'
|
|
41
41
|
|
|
42
42
|
# isort: off
|
|
43
43
|
|
|
@@ -52,6 +52,7 @@ from .types import (
|
|
|
52
52
|
Entry as Entry,
|
|
53
53
|
Content as Content,
|
|
54
54
|
Enclosure as Enclosure,
|
|
55
|
+
EntrySource as EntrySource,
|
|
55
56
|
EntrySearchResult as EntrySearchResult,
|
|
56
57
|
HighlightedString as HighlightedString,
|
|
57
58
|
FeedCounts as FeedCounts,
|
|
@@ -38,6 +38,7 @@ from reader import ReaderError
|
|
|
38
38
|
from reader._plugins import Loader
|
|
39
39
|
from reader.types import _get_entry_content
|
|
40
40
|
from reader.types import TristateFilterInput
|
|
41
|
+
from reader.utils import archive_entries
|
|
41
42
|
|
|
42
43
|
from .api_thing import APIError
|
|
43
44
|
from .api_thing import APIThing
|
|
@@ -104,6 +105,7 @@ def get_reader():
|
|
|
104
105
|
def stream_template(template_name_or_list, **kwargs):
|
|
105
106
|
template = current_app.jinja_env.get_template(template_name_or_list)
|
|
106
107
|
stream = template.stream(**kwargs)
|
|
108
|
+
# TODO: increase to at least 1-2k, like this we have 50% overhead
|
|
107
109
|
stream.enable_buffering(50)
|
|
108
110
|
return Response(stream_with_context(stream))
|
|
109
111
|
|
|
@@ -157,12 +159,8 @@ class EntryProxy:
|
|
|
157
159
|
def title(self):
|
|
158
160
|
highlight = self._search_result.metadata.get('.title')
|
|
159
161
|
if highlight:
|
|
160
|
-
return
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
@property
|
|
164
|
-
def feed(self):
|
|
165
|
-
return FeedProxy(self._search_result, self._entry)
|
|
162
|
+
return highlighted(highlight)
|
|
163
|
+
return self._entry.title
|
|
166
164
|
|
|
167
165
|
@property
|
|
168
166
|
def summary(self):
|
|
@@ -184,21 +182,12 @@ class EntryProxy:
|
|
|
184
182
|
def get_content(self, prefer_summary=False):
|
|
185
183
|
return _get_entry_content(self, prefer_summary)
|
|
186
184
|
|
|
187
|
-
|
|
188
|
-
@dataclass(frozen=True)
|
|
189
|
-
class FeedProxy:
|
|
190
|
-
_search_result: EntrySearchResult
|
|
191
|
-
_entry: Entry
|
|
192
|
-
|
|
193
|
-
def __getattr__(self, name):
|
|
194
|
-
return getattr(self._entry.feed, name)
|
|
195
|
-
|
|
196
185
|
@property
|
|
197
|
-
def
|
|
198
|
-
highlight = self._search_result.metadata.get('.
|
|
186
|
+
def feed_resolved_title(self):
|
|
187
|
+
highlight = self._search_result.metadata.get('.feed_resolved_title')
|
|
199
188
|
if highlight:
|
|
200
|
-
return
|
|
201
|
-
return self._entry.
|
|
189
|
+
return highlighted(highlight)
|
|
190
|
+
return self._entry.feed_resolved_url
|
|
202
191
|
|
|
203
192
|
|
|
204
193
|
@dataclass
|
|
@@ -643,6 +632,14 @@ def mark_all_as_unread(data):
|
|
|
643
632
|
get_reader().mark_entry_as_unread((feed_url, entry_id))
|
|
644
633
|
|
|
645
634
|
|
|
635
|
+
@form_api(really=True)
|
|
636
|
+
@readererror_to_apierror()
|
|
637
|
+
def archive_all(data):
|
|
638
|
+
feed_url = data['feed-url']
|
|
639
|
+
entry_ids = json.loads(data['entry-id'])
|
|
640
|
+
archive_entries(get_reader(), [(feed_url, eid) for eid in entry_ids])
|
|
641
|
+
|
|
642
|
+
|
|
646
643
|
@form_api
|
|
647
644
|
@readererror_to_apierror()
|
|
648
645
|
def mark_as_important(data):
|
|
@@ -840,6 +837,10 @@ def create_app(config):
|
|
|
840
837
|
|
|
841
838
|
app.register_blueprint(blueprint)
|
|
842
839
|
|
|
840
|
+
from . import v2
|
|
841
|
+
|
|
842
|
+
app.register_blueprint(v2.blueprint, url_prefix='/v2')
|
|
843
|
+
|
|
843
844
|
# NOTE: this is part of the app extension API
|
|
844
845
|
app.reader_additional_enclosure_links = []
|
|
845
846
|
app.reader_additional_links = []
|
|
@@ -172,6 +172,9 @@
|
|
|
172
172
|
{{- macros.confirm_button('.form_api', 'mark-all-as-unread', 'all unread', leave_disabled=true, next=next, context={'feed-url': feed.url, 'entry-id': entries_data | tojson}) -}}
|
|
173
173
|
{% endif %}
|
|
174
174
|
|
|
175
|
+
{{- macros.confirm_button('.form_api', 'archive-all', 'archive all', leave_disabled=true, next=url_for('.entries', feed='reader:archived'), context={'feed-url': feed.url, 'entry-id': entries_data | tojson}) -}}
|
|
176
|
+
{# TODO: should redirect to archived # entry #}
|
|
177
|
+
|
|
175
178
|
{{ macros.confirm_button('.form_api', 'delete-feed', 'delete feed', leave_disabled=true, next=url_for('.entries'), context={'feed-url': feed.url}) }}
|
|
176
179
|
|
|
177
180
|
{{ macros.text_input_button('.form_api', 'update-feed-title', 'update feed title', 'feed-title', 'feed title', leave_disabled=true, next=next, context={'feed-url': feed.url}, value=feed.user_title) }}
|
|
@@ -265,7 +268,7 @@
|
|
|
265
268
|
<ul class="controls">
|
|
266
269
|
|
|
267
270
|
<li>
|
|
268
|
-
<a href="{{ url_for('.entries', feed=entry.feed.url) }}">{{
|
|
271
|
+
<a href="{{ url_for('.entries', feed=entry.feed.url) }}">{{ entry.feed_resolved_title or feed.url }}</a>
|
|
269
272
|
<li>
|
|
270
273
|
{%- set published = entry.published or entry.updated_not_none -%}
|
|
271
274
|
<span title="{{ published }}">{{ published | humanize_naturaltime }}</span>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
<li>
|
|
21
21
|
{% if entry.author %} by {{ entry.author }}{% endif %}
|
|
22
|
-
in <a href="{{ url_for('.entries', feed=entry.feed.url) }}">{{
|
|
22
|
+
in <a href="{{ url_for('.entries', feed=entry.feed.url) }}">{{ entry.feed_resolved_title or feed.url }}</a>
|
|
23
23
|
<li>
|
|
24
24
|
{%- set published = entry.published or entry.updated_not_none -%}
|
|
25
25
|
<span title="{{ published }}">{{ published | humanize_naturaltime }}</span>
|
|
@@ -21,6 +21,7 @@ window.onload = function () {
|
|
|
21
21
|
<a href="{{ url_for('reader.feeds') }}">feeds</a>
|
|
22
22
|
<a href="{{ url_for('reader.tags') }}">tags</a>
|
|
23
23
|
<a href="{{ url_for('reader.metadata') }}">metadata</a>
|
|
24
|
+
<a href="{{ url_for('v2.entries') }}">v2</a>
|
|
24
25
|
|
|
25
26
|
{{ macros.text_input_button_get(
|
|
26
27
|
'reader.preview', 'add feed', 'url', 'url',
|
|
@@ -6,17 +6,11 @@
|
|
|
6
6
|
{%- endmacro %}
|
|
7
7
|
|
|
8
8
|
{% macro feed_title(feed) %}
|
|
9
|
-
{
|
|
10
|
-
{{ feed.user_title }}
|
|
11
|
-
{%- elif feed.title and feed.title.strip() -%}
|
|
12
|
-
{{ feed.title }}
|
|
13
|
-
{%- else -%}
|
|
14
|
-
{{ feed.url }}
|
|
15
|
-
{%- endif -%}
|
|
9
|
+
{{- feed.resolved_title or feed.url -}}
|
|
16
10
|
{% endmacro %}
|
|
17
11
|
|
|
18
12
|
{% macro feed_title_secondary(feed) %}
|
|
19
|
-
{%- if feed.
|
|
13
|
+
{%- if feed.title and feed.resolved_title != feed.title -%}
|
|
20
14
|
{{ feed.title }}
|
|
21
15
|
{%- endif -%}
|
|
22
16
|
{% endmacro %}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
from flask import abort
|
|
5
|
+
from flask import Blueprint
|
|
6
|
+
from flask import current_app
|
|
7
|
+
from flask import redirect
|
|
8
|
+
from flask import request
|
|
9
|
+
from flask import url_for
|
|
10
|
+
from jinja2_fragments.flask import render_block
|
|
11
|
+
|
|
12
|
+
from reader import InvalidSearchQueryError
|
|
13
|
+
|
|
14
|
+
from .. import EntryProxy
|
|
15
|
+
from .. import get_reader
|
|
16
|
+
from .. import stream_template
|
|
17
|
+
from .forms import EntryFilter
|
|
18
|
+
from .forms import SearchEntryFilter
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
blueprint = Blueprint(
|
|
22
|
+
'v2', __name__, template_folder='templates', static_folder='static'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@blueprint.route('/')
|
|
27
|
+
def entries():
|
|
28
|
+
reader = get_reader()
|
|
29
|
+
|
|
30
|
+
# TODO: search improvements
|
|
31
|
+
# TODO: paqgination
|
|
32
|
+
# TODO: read time
|
|
33
|
+
|
|
34
|
+
if request.args.get('q', '').strip():
|
|
35
|
+
form = SearchEntryFilter(request.args)
|
|
36
|
+
else:
|
|
37
|
+
form = EntryFilter(request.args)
|
|
38
|
+
|
|
39
|
+
form_args = form.args
|
|
40
|
+
if q := form_args.pop('Q', ''):
|
|
41
|
+
form_args['q'] = q
|
|
42
|
+
return redirect(url_for('.entries', **form_args))
|
|
43
|
+
if form_args != request.args.to_dict():
|
|
44
|
+
return redirect(url_for('.entries', **form_args))
|
|
45
|
+
|
|
46
|
+
feed = None
|
|
47
|
+
if form.feed.data:
|
|
48
|
+
feed = reader.get_feed(form.feed.data, None)
|
|
49
|
+
if not feed:
|
|
50
|
+
abort(404)
|
|
51
|
+
|
|
52
|
+
kwargs = dict(form.data)
|
|
53
|
+
if query := kwargs.pop('search', None):
|
|
54
|
+
|
|
55
|
+
def get_entries(**kwargs):
|
|
56
|
+
for sr in reader.search_entries(query, **kwargs):
|
|
57
|
+
yield EntryProxy(sr, reader.get_entry(sr))
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
get_entries = reader.get_entries
|
|
61
|
+
|
|
62
|
+
entries = []
|
|
63
|
+
if form.validate():
|
|
64
|
+
try:
|
|
65
|
+
entries = eager_iterator(get_entries(**kwargs, limit=64))
|
|
66
|
+
except StopIteration:
|
|
67
|
+
pass
|
|
68
|
+
except InvalidSearchQueryError as e:
|
|
69
|
+
form.search.errors.append(f"invalid query: {e}")
|
|
70
|
+
|
|
71
|
+
return stream_template(
|
|
72
|
+
'v2/entries.html',
|
|
73
|
+
form=form,
|
|
74
|
+
entries=entries,
|
|
75
|
+
feed=feed,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def eager_iterator(it):
|
|
80
|
+
it = iter(it)
|
|
81
|
+
try:
|
|
82
|
+
return itertools.chain([next(it)], it)
|
|
83
|
+
except StopIteration:
|
|
84
|
+
return it
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@blueprint.route('/mark-as', methods=['POST'])
|
|
88
|
+
def mark_as():
|
|
89
|
+
reader = get_reader()
|
|
90
|
+
|
|
91
|
+
entry = request.form['feed-url'], request.form['entry-id']
|
|
92
|
+
|
|
93
|
+
if 'read' in request.form:
|
|
94
|
+
match request.form['read']:
|
|
95
|
+
case 'true':
|
|
96
|
+
reader.set_entry_read(entry, True)
|
|
97
|
+
case 'false':
|
|
98
|
+
reader.set_entry_read(entry, False)
|
|
99
|
+
case _:
|
|
100
|
+
abort(422)
|
|
101
|
+
|
|
102
|
+
if 'important' in request.form:
|
|
103
|
+
match request.form['important']:
|
|
104
|
+
case 'true':
|
|
105
|
+
reader.set_entry_important(entry, True)
|
|
106
|
+
case 'false':
|
|
107
|
+
reader.set_entry_important(entry, False)
|
|
108
|
+
case 'none':
|
|
109
|
+
reader.set_entry_important(entry, None)
|
|
110
|
+
case _:
|
|
111
|
+
abort(422)
|
|
112
|
+
|
|
113
|
+
if request.headers.get('hx-request') == 'true':
|
|
114
|
+
return render_block(
|
|
115
|
+
'v2/entries.html',
|
|
116
|
+
'entry_form',
|
|
117
|
+
entry=reader.get_entry(entry),
|
|
118
|
+
next=request.form['next'],
|
|
119
|
+
# equivalent to {% import "v2/macros.html" as macros %}
|
|
120
|
+
macros=current_app.jinja_env.get_template('v2/macros.html').module,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return redirect(request.form['next'], code=303)
|