reader 3.23__tar.gz → 3.24__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.23 → reader-3.24}/CHANGES.rst +46 -0
- {reader-3.23/src/reader.egg-info → reader-3.24}/PKG-INFO +3 -3
- {reader-3.23 → reader-3.24}/docs/api.rst +8 -0
- {reader-3.23 → reader-3.24}/docs/app.rst +11 -0
- {reader-3.23 → reader-3.24}/docs/conf.py +3 -0
- {reader-3.23 → reader-3.24}/docs/guide.rst +31 -2
- {reader-3.23 → reader-3.24}/docs/install.rst +2 -1
- reader-3.24/docs/screenshots/empty.png +0 -0
- reader-3.24/docs/screenshots/help.png +0 -0
- reader-3.24/docs/screenshots/import-select.png +0 -0
- {reader-3.23 → reader-3.24}/pyproject.toml +7 -3
- {reader-3.23 → reader-3.24}/src/reader/__init__.py +4 -1
- {reader-3.23 → reader-3.24}/src/reader/_app/__init__.py +14 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/templates/entry.html +9 -1
- {reader-3.23 → reader-3.24}/src/reader/_app/static/style.css +6 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/add_feed.html +1 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/change_feed_title.html +1 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/delete_feed.html +1 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/entries.html +16 -1
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/entry.html +1 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/feeds.html +5 -1
- reader-3.24/src/reader/_app/templates/help.html +131 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/import_feeds.html +12 -2
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/layout_base.html +11 -1
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/macros.html +41 -3
- {reader-3.23 → reader-3.24}/src/reader/_cli.py +17 -6
- {reader-3.23 → reader-3.24}/src/reader/_config_utils.py +7 -3
- reader-3.24/src/reader/_logging.py +80 -0
- {reader-3.23 → reader-3.24}/src/reader/_parser/__init__.py +17 -9
- {reader-3.23 → reader-3.24}/src/reader/_parser/_lazy.py +50 -71
- {reader-3.23 → reader-3.24}/src/reader/_parser/feedparser.py +93 -12
- {reader-3.23 → reader-3.24}/src/reader/_parser/jsonfeed.py +29 -31
- {reader-3.23 → reader-3.24}/src/reader/_plugins/legacy/enclosure_tags.py +1 -1
- {reader-3.23 → reader-3.24}/src/reader/_storage/_entries.py +55 -19
- {reader-3.23 → reader-3.24}/src/reader/_storage/_feeds.py +65 -39
- {reader-3.23 → reader-3.24}/src/reader/_storage/_schema.py +45 -1
- {reader-3.23 → reader-3.24}/src/reader/_storage/_search.py +3 -13
- {reader-3.23 → reader-3.24}/src/reader/_storage/_sqlite_utils.py +2 -0
- {reader-3.23 → reader-3.24}/src/reader/_types.py +15 -99
- reader-3.23/src/reader/_update.py → reader-3.24/src/reader/_update/__init__.py +95 -196
- reader-3.24/src/reader/_update/base.py +205 -0
- reader-3.24/src/reader/_update/hooks.py +105 -0
- {reader-3.23 → reader-3.24}/src/reader/_utils.py +25 -61
- {reader-3.23 → reader-3.24}/src/reader/core.py +7 -36
- {reader-3.23 → reader-3.24}/src/reader/plugins/entry_dedupe.py +29 -40
- {reader-3.23 → reader-3.24}/src/reader/plugins/mark_as_read.py +5 -7
- {reader-3.23 → reader-3.24}/src/reader/plugins/readtime.py +8 -10
- {reader-3.23 → reader-3.24}/src/reader/plugins/ua_fallback.py +3 -10
- {reader-3.23 → reader-3.24}/src/reader/types.py +64 -9
- {reader-3.23 → reader-3.24/src/reader.egg-info}/PKG-INFO +3 -3
- {reader-3.23 → reader-3.24}/src/reader.egg-info/SOURCES.txt +9 -1
- {reader-3.23 → reader-3.24}/src/reader.egg-info/requires.txt +2 -2
- {reader-3.23 → reader-3.24}/tests/conftest.py +3 -39
- {reader-3.23 → reader-3.24}/tests/data/empty.atom.py +1 -0
- {reader-3.23 → reader-3.24}/tests/data/full.atom.py +4 -3
- {reader-3.23 → reader-3.24}/tests/data/full.json.py +7 -3
- {reader-3.23 → reader-3.24}/tests/data/full.rss.py +3 -2
- {reader-3.23 → reader-3.24}/tests/data/invalid.json.py +3 -2
- reader-3.24/tests/fixtures.py +48 -0
- {reader-3.23 → reader-3.24}/tests/test__types.py +14 -5
- {reader-3.23 → reader-3.24}/tests/test__utils.py +0 -19
- {reader-3.23 → reader-3.24}/tests/test_cli.py +18 -4
- {reader-3.23 → reader-3.24}/tests/test_config_utils.py +8 -0
- {reader-3.23 → reader-3.24}/tests/test_lazy_imports.py +0 -2
- reader-3.24/tests/test_logging.py +41 -0
- {reader-3.23 → reader-3.24}/tests/test_parser.py +201 -13
- {reader-3.23 → reader-3.24}/tests/test_plugins_ua_fallback.py +1 -1
- {reader-3.23 → reader-3.24}/tests/test_reader.py +16 -13
- {reader-3.23 → reader-3.24}/tests/test_reader_search.py +7 -2
- {reader-3.23 → reader-3.24}/tests/test_reader_update.py +11 -9
- {reader-3.23 → reader-3.24}/tests/test_storage.py +28 -0
- {reader-3.23 → reader-3.24}/tests/test_types.py +33 -0
- reader-3.23/docs/screenshots/import-select.png +0 -0
- {reader-3.23 → reader-3.24}/LICENSE +0 -0
- {reader-3.23 → reader-3.24}/MANIFEST.in +0 -0
- {reader-3.23 → reader-3.24}/README.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/changelog.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/cli.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/config.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/contributing.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/dev.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/index.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/internal.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/plugins.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/screenshots/entries-filters-light.png +0 -0
- {reader-3.23 → reader-3.24}/docs/screenshots/entries.png +0 -0
- {reader-3.23 → reader-3.24}/docs/screenshots/entry-image-light.png +0 -0
- {reader-3.23 → reader-3.24}/docs/screenshots/entry.png +0 -0
- {reader-3.23 → reader-3.24}/docs/screenshots/feed.png +0 -0
- {reader-3.23 → reader-3.24}/docs/screenshots/feeds.png +0 -0
- {reader-3.23 → reader-3.24}/docs/screenshots/import-result.png +0 -0
- {reader-3.23 → reader-3.24}/docs/tutorial.rst +0 -0
- {reader-3.23 → reader-3.24}/docs/why.rst +0 -0
- {reader-3.23 → reader-3.24}/examples/config.toml +0 -0
- {reader-3.23 → reader-3.24}/examples/custom_headers.py +0 -0
- {reader-3.23 → reader-3.24}/examples/feed_slugs.py +0 -0
- {reader-3.23 → reader-3.24}/examples/parser_only.py +0 -0
- {reader-3.23 → reader-3.24}/examples/podcast.py +0 -0
- {reader-3.23 → reader-3.24}/examples/terminal.py +0 -0
- {reader-3.23 → reader-3.24}/run.sh +0 -0
- {reader-3.23 → reader-3.24}/scripts/backup.sh +0 -0
- {reader-3.23 → reader-3.24}/scripts/bench.py +0 -0
- {reader-3.23 → reader-3.24}/scripts/debug_storage_stats.py +0 -0
- {reader-3.23 → reader-3.24}/scripts/entry_dedupe_backtest.py +0 -0
- {reader-3.23 → reader-3.24}/scripts/jscontrols.html +0 -0
- {reader-3.23 → reader-3.24}/scripts/jscontrols.py +0 -0
- {reader-3.23 → reader-3.24}/scripts/lines.sh +0 -0
- {reader-3.23 → reader-3.24}/scripts/release.py +0 -0
- {reader-3.23 → reader-3.24}/scripts/vendor_app_stuff.py +0 -0
- {reader-3.23 → reader-3.24}/setup.cfg +0 -0
- {reader-3.23 → reader-3.24}/src/reader/__main__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/cli.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/ext.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/forms.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/api_thing.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/static/controls.js +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/static/style.css +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/templates/add_entry.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/templates/entries.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/templates/feeds.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/templates/layout.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/templates/macros.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/templates/metadata.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/templates/tags.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/legacy/wsgi.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/theme.js +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/vendor/README +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/css/bootstrap.min.css +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/vendor/htmx-ext-response-targets@2.0.4/dist/response-targets.min.js +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/static/vendor/htmx.org@2.0.8/dist/htmx.min.js +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_app/wsgi.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_hash_utils.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_parser/_http_utils.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_parser/_url_utils.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_parser/file.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_parser/http.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_parser/requests/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_parser/requests/_lazy.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_plugins/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_plugins/cli_status.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_plugins/legacy/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_plugins/legacy/preview_feed_list.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_plugins/legacy/share.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_plugins/legacy/templates/preview_feed_list.html +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_plugins/sqlite_releases.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_plugins/timer.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_storage/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_storage/_base.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_storage/_changes.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_storage/_html_utils.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_storage/_sql_utils.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_storage/_tags.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/api.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/encodings.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/exceptions.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/html.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/http.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/mixin.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/sgml.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/urls.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/_vendor/feedparser/util.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/exceptions.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/opml.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/plugins/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/plugins/_loader.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/plugins/enclosure_dedupe.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader/py.typed +0 -0
- {reader-3.23 → reader-3.24}/src/reader/utils.py +0 -0
- {reader-3.23 → reader-3.24}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.23 → reader-3.24}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.23 → reader-3.24}/tests/_plugins/__init__.py +0 -0
- {reader-3.23 → reader-3.24}/tests/_plugins/builtin.py +0 -0
- {reader-3.23 → reader-3.24}/tests/_plugins/error_during_import.py +0 -0
- {reader-3.23 → reader-3.24}/tests/_plugins/error_import_error.py +0 -0
- {reader-3.23 → reader-3.24}/tests/data/10.json +0 -0
- {reader-3.23 → reader-3.24}/tests/data/10.json.py +0 -0
- {reader-3.23 → reader-3.24}/tests/data/custom +0 -0
- {reader-3.23 → reader-3.24}/tests/data/empty.atom +0 -0
- {reader-3.23 → reader-3.24}/tests/data/empty.json +0 -0
- {reader-3.23 → reader-3.24}/tests/data/empty.json.py +0 -0
- {reader-3.23 → reader-3.24}/tests/data/empty.rss +0 -0
- {reader-3.23 → reader-3.24}/tests/data/empty.rss.py +0 -0
- {reader-3.23 → reader-3.24}/tests/data/full.atom +0 -0
- {reader-3.23 → reader-3.24}/tests/data/full.json +0 -0
- {reader-3.23 → reader-3.24}/tests/data/full.rss +0 -0
- {reader-3.23 → reader-3.24}/tests/data/invalid.json +0 -0
- {reader-3.23 → reader-3.24}/tests/data/relative.atom +0 -0
- {reader-3.23 → reader-3.24}/tests/data/relative.atom.py +0 -0
- {reader-3.23 → reader-3.24}/tests/data/relative.rss +0 -0
- {reader-3.23 → reader-3.24}/tests/data/relative.rss.py +0 -0
- {reader-3.23 → reader-3.24}/tests/data/sqlite_releases.html +0 -0
- {reader-3.23 → reader-3.24}/tests/data/unknown.json +0 -0
- {reader-3.23 → reader-3.24}/tests/data/unknown.json.py +0 -0
- {reader-3.23 → reader-3.24}/tests/fakeparser.py +0 -0
- {reader-3.23 → reader-3.24}/tests/reader_methods.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_app_legacy.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_app_wsgi.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_bench.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_changes.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_exceptions.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_hash_utils.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_html_utils.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_opml.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_plugins_cli_status.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_plugins_enclosure_dedupe.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_plugins_entry_dedupe.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_plugins_loader.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_plugins_mark_as_read.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_plugins_preview_feed_list.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_plugins_readtime.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_plugins_sqlite_releases.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_counts.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_deprecations.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_filter.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_hooks.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_integration.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_lifecycle.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_private.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_sort.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_reader_utils.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_search.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_sql_utils.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_sqlite_utils.py +0 -0
- {reader-3.23 → reader-3.24}/tests/test_tags.py +0 -0
- {reader-3.23 → reader-3.24}/tests/utils.py +0 -0
- {reader-3.23 → reader-3.24}/tox.ini +0 -0
|
@@ -6,6 +6,52 @@ Changelog
|
|
|
6
6
|
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
Version 3.24
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2026-05-20
|
|
13
|
+
|
|
14
|
+
* Support multiple authors with details (link, email).
|
|
15
|
+
Thanks to `Anshul Mittal`_ for the PR.
|
|
16
|
+
(:issue:`391`)
|
|
17
|
+
|
|
18
|
+
* Add :attr:`Feed.authors`, :attr:`Entry.authors`,
|
|
19
|
+
and :attr:`EntrySource.authors` attributes.
|
|
20
|
+
* Add matching :attr:`~Feed.authors_str` property.
|
|
21
|
+
* Deprecate :attr:`~Feed.author` attribute.
|
|
22
|
+
|
|
23
|
+
* Fix bug introduced in `version 3.15 <Version 3.15_>`__
|
|
24
|
+
causing feeds to not be retrieved in parallel
|
|
25
|
+
even if :meth:`~Reader.update_feeds` was called with ``workers`` > 1.
|
|
26
|
+
|
|
27
|
+
* Use :ref:`structured logging <logging>` internally,
|
|
28
|
+
with the possibility to opt into native `structlog`_ logging.
|
|
29
|
+
(:issue:`147`)
|
|
30
|
+
|
|
31
|
+
* Use :meth:`datetime.datetime.fromisoformat` instead of external dependency ``iso8601``.
|
|
32
|
+
Thanks to `Sebastian Bradley`_ for the PR.
|
|
33
|
+
(:issue:`398`)
|
|
34
|
+
|
|
35
|
+
* Refactor updater internals to allow reusing logic in alternate implementations.
|
|
36
|
+
|
|
37
|
+
* :doc:`app` improvements.
|
|
38
|
+
|
|
39
|
+
* Add empty state and contextual help; :ref:`screenshots <screenshots-help>`.
|
|
40
|
+
* Import feeds improvements (select all/none, accept only XML / OPML files).
|
|
41
|
+
|
|
42
|
+
* Add ``--json`` option to the ``list`` CLI commands.
|
|
43
|
+
Thanks to `Puneet Dixit`_ for the PR.
|
|
44
|
+
(:issue:`394`)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
.. _Puneet Dixit: https://github.com/puneetdixit200
|
|
49
|
+
.. _Anshul Mittal: https://github.com/anderson688
|
|
50
|
+
.. _Sebastian Bradley: https://github.com/bazbrad765
|
|
51
|
+
|
|
52
|
+
.. _structlog: https://www.structlog.org/
|
|
53
|
+
|
|
54
|
+
|
|
9
55
|
Version 3.23
|
|
10
56
|
------------
|
|
11
57
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.24
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -39,12 +39,12 @@ Requires-Dist: typing-extensions>=4
|
|
|
39
39
|
Requires-Dist: feedparser>=6
|
|
40
40
|
Requires-Dist: requests>=2.18
|
|
41
41
|
Requires-Dist: werkzeug>2
|
|
42
|
-
Requires-Dist: iso8601>=1
|
|
43
42
|
Requires-Dist: beautifulsoup4>=4.5
|
|
43
|
+
Requires-Dist: structlog
|
|
44
44
|
Provides-Extra: all
|
|
45
45
|
Requires-Dist: reader[app,cli,unstable-plugins]; extra == "all"
|
|
46
46
|
Provides-Extra: cli
|
|
47
|
-
Requires-Dist: click
|
|
47
|
+
Requires-Dist: click!=8.4.0,>=7; extra == "cli"
|
|
48
48
|
Provides-Extra: app
|
|
49
49
|
Requires-Dist: flask>=0.10; extra == "app"
|
|
50
50
|
Requires-Dist: humanize>=4; extra == "app"
|
|
@@ -27,12 +27,17 @@ Data objects
|
|
|
27
27
|
|
|
28
28
|
.. autoclass:: Feed
|
|
29
29
|
:members:
|
|
30
|
+
:inherited-members:
|
|
31
|
+
|
|
32
|
+
.. autoclass:: Author
|
|
33
|
+
:members:
|
|
30
34
|
|
|
31
35
|
.. autoclass:: ExceptionInfo
|
|
32
36
|
:members:
|
|
33
37
|
|
|
34
38
|
.. autoclass:: Entry
|
|
35
39
|
:members:
|
|
40
|
+
:inherited-members:
|
|
36
41
|
|
|
37
42
|
.. autoclass:: Content
|
|
38
43
|
:members:
|
|
@@ -42,6 +47,7 @@ Data objects
|
|
|
42
47
|
|
|
43
48
|
.. autoclass:: EntrySource
|
|
44
49
|
:members:
|
|
50
|
+
:inherited-members:
|
|
45
51
|
|
|
46
52
|
.. autoclass:: EntrySearchResult
|
|
47
53
|
:members:
|
|
@@ -227,6 +233,8 @@ Constants
|
|
|
227
233
|
Utilities
|
|
228
234
|
---------
|
|
229
235
|
|
|
236
|
+
.. autofunction:: reader.enable_structlog
|
|
237
|
+
|
|
230
238
|
.. autofunction:: reader.utils.archive_entries
|
|
231
239
|
|
|
232
240
|
|
|
@@ -74,6 +74,17 @@ Screenshots
|
|
|
74
74
|
|
|
75
75
|
import feeds (result)
|
|
76
76
|
|
|
77
|
+
.. _screenshots-help:
|
|
78
|
+
|
|
79
|
+
.. figure:: screenshots/empty.png
|
|
80
|
+
:width: 240px
|
|
81
|
+
|
|
82
|
+
new user / empty state
|
|
83
|
+
|
|
84
|
+
.. figure:: screenshots/help.png
|
|
85
|
+
:width: 240px
|
|
86
|
+
|
|
87
|
+
contextual help
|
|
77
88
|
|
|
78
89
|
|
|
79
90
|
Serving the web app
|
|
@@ -374,7 +374,7 @@ As seen in the previous sections,
|
|
|
374
374
|
updated=datetime.datetime(2020, 2, 28, 9, 34, 2, tzinfo=datetime.timezone.utc),
|
|
375
375
|
title='Hello Internet',
|
|
376
376
|
link='http://www.hellointernet.fm/',
|
|
377
|
-
|
|
377
|
+
authors=(Author(name='CGP Grey'),),
|
|
378
378
|
subtitle='CGP Grey and Brady Haran talk about YouTube, life, work, whatever.',
|
|
379
379
|
version='rss20',
|
|
380
380
|
user_title=None,
|
|
@@ -386,9 +386,10 @@ As seen in the previous sections,
|
|
|
386
386
|
To get all the feeds, use the :meth:`~Reader.get_feeds` method::
|
|
387
387
|
|
|
388
388
|
>>> for feed in reader.get_feeds():
|
|
389
|
+
... authors = ", ".join(a.name for a in feed.authors if a.name) or 'unknown author'
|
|
389
390
|
... print(
|
|
390
391
|
... feed.title or feed.url,
|
|
391
|
-
... f"by {
|
|
392
|
+
... f"by {authors},",
|
|
392
393
|
... f"updated on {feed.updated or 'never'}",
|
|
393
394
|
... )
|
|
394
395
|
...
|
|
@@ -1001,6 +1002,34 @@ depending on their type attribute or feedparser defaults:
|
|
|
1001
1002
|
|
|
1002
1003
|
|
|
1003
1004
|
|
|
1005
|
+
.. _logging:
|
|
1006
|
+
|
|
1007
|
+
Logging
|
|
1008
|
+
-------
|
|
1009
|
+
|
|
1010
|
+
*reader* uses structured logging internally, through `structlog`_.
|
|
1011
|
+
|
|
1012
|
+
By default, output goes to the standard library :mod:`logging` module;
|
|
1013
|
+
if you don't care about structlog,
|
|
1014
|
+
configure :mod:`logging` as you normally would.
|
|
1015
|
+
The *reader* root logger is called ``reader``;
|
|
1016
|
+
the names of its children are an implementation detail,
|
|
1017
|
+
and may change without any notice.
|
|
1018
|
+
|
|
1019
|
+
To enable native structlog logging,
|
|
1020
|
+
call :func:`enable_structlog` **before** configuring structlog::
|
|
1021
|
+
|
|
1022
|
+
import reader, structlog
|
|
1023
|
+
|
|
1024
|
+
reader.enable_structlog()
|
|
1025
|
+
structlog.configure(...)
|
|
1026
|
+
|
|
1027
|
+
... # do stuff with reader
|
|
1028
|
+
|
|
1029
|
+
.. _structlog: https://www.structlog.org/
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
|
|
1004
1033
|
.. _errors:
|
|
1005
1034
|
|
|
1006
1035
|
Errors and exceptions
|
|
@@ -18,9 +18,9 @@ These packages will be installed automatically when installing *reader*:
|
|
|
18
18
|
it replaces feedparser's default use of :mod:`urllib`
|
|
19
19
|
to make it easier to write plugins.
|
|
20
20
|
* `werkzeug`_ provides HTTP utilities.
|
|
21
|
-
* `iso8601`_ parses dates in ISO 8601 / RFC 3339; used for JSON Feed parsing.
|
|
22
21
|
* `beautifulsoup4`_ is used to strip HTML tags before adding entries
|
|
23
22
|
to the search index.
|
|
23
|
+
* `structlog`_ is used for structured logging.
|
|
24
24
|
* `typing-extensions`_ is used for :mod:`typing` backports.
|
|
25
25
|
|
|
26
26
|
*reader* also depends on the :mod:`sqlite3` standard library module
|
|
@@ -64,6 +64,7 @@ As of version |version|, *reader* has the following extras:
|
|
|
64
64
|
.. _requests: https://requests.readthedocs.io/
|
|
65
65
|
.. _werkzeug: https://werkzeug.palletsprojects.com/
|
|
66
66
|
.. _iso8601: http://pyiso8601.readthedocs.org/
|
|
67
|
+
.. _structlog: https://www.structlog.org/
|
|
67
68
|
.. _typing-extensions: https://pypi.org/project/typing-extensions/
|
|
68
69
|
.. _JSON1: https://www.sqlite.org/json1.html
|
|
69
70
|
.. _FTS5: https://www.sqlite.org/fts5.html
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -40,10 +40,9 @@ dependencies = [
|
|
|
40
40
|
"requests>=2.18",
|
|
41
41
|
# for _http_utils
|
|
42
42
|
"werkzeug>2",
|
|
43
|
-
# for JSON Feed date parsing
|
|
44
|
-
"iso8601>=1",
|
|
45
43
|
# search
|
|
46
44
|
"beautifulsoup4>=4.5",
|
|
45
|
+
"structlog",
|
|
47
46
|
]
|
|
48
47
|
dynamic = ["version"]
|
|
49
48
|
|
|
@@ -60,7 +59,8 @@ Changelog = "https://reader.readthedocs.io/en/latest/changelog.html"
|
|
|
60
59
|
all = ["reader[cli,app,unstable-plugins]"]
|
|
61
60
|
|
|
62
61
|
cli = [
|
|
63
|
-
|
|
62
|
+
# != 8.4.0 because of https://github.com/pallets/click/issues/3458
|
|
63
|
+
"click>=7,!=8.4.0",
|
|
64
64
|
]
|
|
65
65
|
app = [
|
|
66
66
|
"flask>=0.10",
|
|
@@ -190,6 +190,10 @@ module = [
|
|
|
190
190
|
]
|
|
191
191
|
ignore_errors = true
|
|
192
192
|
|
|
193
|
+
[tool.black]
|
|
194
|
+
skip-string-normalization = true
|
|
195
|
+
extend-exclude = "src/reader/_vendor/.*"
|
|
196
|
+
|
|
193
197
|
[tool.isort]
|
|
194
198
|
profile = "black"
|
|
195
199
|
py_version = 311
|
|
@@ -37,7 +37,7 @@ Print the titles of the unread entries::
|
|
|
37
37
|
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
__version__ = '3.
|
|
40
|
+
__version__ = '3.24'
|
|
41
41
|
|
|
42
42
|
# isort: off
|
|
43
43
|
|
|
@@ -67,6 +67,7 @@ from .types import (
|
|
|
67
67
|
FeedToImport as FeedToImport,
|
|
68
68
|
FeedImportResult as FeedImportResult,
|
|
69
69
|
FeedExport as FeedExport,
|
|
70
|
+
Author as Author,
|
|
70
71
|
)
|
|
71
72
|
|
|
72
73
|
from .exceptions import (
|
|
@@ -106,6 +107,8 @@ USER_AGENT = f'python-reader/{__version__} (+{SOURCE_URL})'
|
|
|
106
107
|
# Prevent any logging output by default. If no handler is set,
|
|
107
108
|
# the messages bubble up to the root logger and get printed on stderr.
|
|
108
109
|
# https://docs.python.org/3/howto/logging.html#library-config
|
|
110
|
+
|
|
109
111
|
import logging # noqa: E402
|
|
112
|
+
from ._logging import enable_structlog as enable_structlog # noqa: E402
|
|
110
113
|
|
|
111
114
|
logging.getLogger('reader').addHandler(logging.NullHandler())
|
|
@@ -290,6 +290,10 @@ def import_feeds():
|
|
|
290
290
|
parsed_feeds = [asdict(f) for f in opml.parse(file.stream)]
|
|
291
291
|
except opml.OPMLError as e:
|
|
292
292
|
error = str(e)
|
|
293
|
+
else:
|
|
294
|
+
if not parsed_feeds:
|
|
295
|
+
parsed_feeds = None
|
|
296
|
+
error = "file contains no feeds"
|
|
293
297
|
else:
|
|
294
298
|
feeds = (
|
|
295
299
|
FeedToImport(**json.loads(f)) for f in request.form.getlist('feed')
|
|
@@ -312,6 +316,11 @@ def entry():
|
|
|
312
316
|
return render_template('entry.html', entry=entry)
|
|
313
317
|
|
|
314
318
|
|
|
319
|
+
@blueprint.route('/help')
|
|
320
|
+
def help():
|
|
321
|
+
return render_template('help.html')
|
|
322
|
+
|
|
323
|
+
|
|
315
324
|
def stream_template(template_name_or_list, **kwargs):
|
|
316
325
|
# Ensure flashed messages get removed from the session,
|
|
317
326
|
# otherwise they keep adding up and never disappear.
|
|
@@ -385,6 +394,11 @@ def _find_static(folder, filename):
|
|
|
385
394
|
return matches[0]
|
|
386
395
|
|
|
387
396
|
|
|
397
|
+
@blueprint.app_template_global()
|
|
398
|
+
def is_new_user():
|
|
399
|
+
return not list(get_reader().get_feeds(limit=1))
|
|
400
|
+
|
|
401
|
+
|
|
388
402
|
def create_app(reader_config):
|
|
389
403
|
app = Flask(__name__)
|
|
390
404
|
app.config['SECRET_KEY'] = 'secret'
|
|
@@ -18,7 +18,15 @@
|
|
|
18
18
|
<ul class="controls">
|
|
19
19
|
|
|
20
20
|
<li>
|
|
21
|
-
{% if entry.
|
|
21
|
+
{% if entry.authors %}
|
|
22
|
+
by
|
|
23
|
+
{% for author in entry.authors -%}
|
|
24
|
+
{% if author.href %}<a href="{{ author.href }}" title="{{ author.email or '' }}">{% endif %}
|
|
25
|
+
{{- author.name or author.email or 'unknown' -}}
|
|
26
|
+
{% if author.href %}</a>{% endif %}
|
|
27
|
+
{%- if not loop.last %}, {% endif %}
|
|
28
|
+
{%- endfor %}
|
|
29
|
+
{% endif %}
|
|
22
30
|
in <a href="{{ url_for('.entries', feed=entry.feed.url) }}">{{ entry.feed_resolved_title or feed.url }}</a>
|
|
23
31
|
<li>
|
|
24
32
|
{%- set published = entry.published or entry.updated_not_none -%}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
{% set help_topic = 'entries' if request.args else None %}
|
|
1
2
|
{% extends "layout.html" %}
|
|
2
3
|
|
|
3
4
|
{% import "macros.html" as macros %}
|
|
@@ -142,6 +143,17 @@
|
|
|
142
143
|
<i class="bi bi-{{ macros.bs_file_icon(enclosure.type) }}"></i>
|
|
143
144
|
</a>
|
|
144
145
|
{%- endfor %}
|
|
146
|
+
{%- if loop is defined and loop.first %}
|
|
147
|
+
<a
|
|
148
|
+
href="{{ url_for('.help', _anchor='entry-flags') }}"
|
|
149
|
+
target="_blank"
|
|
150
|
+
title="help: entry flags"
|
|
151
|
+
class="btn text-secondary"
|
|
152
|
+
role="button"
|
|
153
|
+
>
|
|
154
|
+
<i class="bi bi-question-lg"></i>
|
|
155
|
+
</a>
|
|
156
|
+
{%- endif %}
|
|
145
157
|
</div>
|
|
146
158
|
|
|
147
159
|
{% endblock %}
|
|
@@ -150,7 +162,10 @@
|
|
|
150
162
|
</div>
|
|
151
163
|
|
|
152
164
|
{% else %}
|
|
153
|
-
<p>
|
|
165
|
+
<p>No entries found.</p>
|
|
166
|
+
{%- if is_new_user() %}
|
|
167
|
+
{{ macros.new_user_help_alert() }}
|
|
168
|
+
{% endif %}
|
|
154
169
|
{% endfor %}
|
|
155
170
|
|
|
156
171
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
{% set help_topic = 'feeds' %}
|
|
1
2
|
{% extends "layout.html" %}
|
|
2
3
|
|
|
3
4
|
{% import "macros.html" as macros %}
|
|
@@ -76,7 +77,10 @@
|
|
|
76
77
|
</div>
|
|
77
78
|
|
|
78
79
|
{% else %}
|
|
79
|
-
<p>
|
|
80
|
+
<p>No feeds found.</p>
|
|
81
|
+
{%- if is_new_user() %}
|
|
82
|
+
{{ macros.new_user_help_alert() }}
|
|
83
|
+
{%- endif %}
|
|
80
84
|
{% endfor %}
|
|
81
85
|
|
|
82
86
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{% extends "layout.html" %}
|
|
2
|
+
|
|
3
|
+
{% import "macros.html" as macros %}
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
{% block page_title %}
|
|
7
|
+
Help - {{ super() }}
|
|
8
|
+
{% endblock %}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
{% block body %}
|
|
12
|
+
|
|
13
|
+
<h1>Help</h1>
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
<p>
|
|
17
|
+
Welcome to <strong>reader</strong>, a feed reader web app!
|
|
18
|
+
|
|
19
|
+
<p>
|
|
20
|
+
<strong>reader</strong> allows you to
|
|
21
|
+
subscribe to web feeds,
|
|
22
|
+
read articles,
|
|
23
|
+
and keep track of the ones you've read or liked.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<h2 id="feeds">Feeds</h2>
|
|
27
|
+
|
|
28
|
+
<p>
|
|
29
|
+
A <a href="https://en.wikipedia.org/wiki/Web_feed">web feed</a>
|
|
30
|
+
is an internet document used to get updates
|
|
31
|
+
from websites such as blogs, web comics, or podcasts.
|
|
32
|
+
|
|
33
|
+
<p>
|
|
34
|
+
When you subscribe to a feed,
|
|
35
|
+
<em>reader</em> checks it regularly to get the latest articles
|
|
36
|
+
(unless the feed is disabled).
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
<h3 id="add-feed">Adding feeds</h3>
|
|
40
|
+
|
|
41
|
+
<p>
|
|
42
|
+
To add a feed, paste its link in the
|
|
43
|
+
<a href="{{ url_for('.add_feed') }}">Add feed</a> page.
|
|
44
|
+
|
|
45
|
+
<p>
|
|
46
|
+
You can usually find if a website has a feed
|
|
47
|
+
by looking in its header or footer for this icon
|
|
48
|
+
<i class="bi bi-rss" aria-label="RSS logo"></i>,
|
|
49
|
+
or for links called <em>feed</em>, <em>RSS</em>, or <em>Atom</em>.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
<h3 id="import-export">Importing / exporting feeds</h3>
|
|
53
|
+
|
|
54
|
+
<p>
|
|
55
|
+
You can <a href="{{ url_for('.import_feeds') }}">import feeds</a>
|
|
56
|
+
from a subscription list exported by another feed reader.
|
|
57
|
+
|
|
58
|
+
<p>
|
|
59
|
+
To export your own feeds as a subscription list, click the
|
|
60
|
+
<i class="bi bi-filetype-xml" aria-label="export as OPML"></i> icon
|
|
61
|
+
on the <a href="{{ url_for('.feeds') }}">Feeds</a> page.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
<h2 id="entries">Entries</h2>
|
|
65
|
+
|
|
66
|
+
<p>
|
|
67
|
+
An entry is an item from a feed –
|
|
68
|
+
an article, a podcast episode, etc.
|
|
69
|
+
|
|
70
|
+
<p>
|
|
71
|
+
To read an entry, click its title on the
|
|
72
|
+
<a href="{{ url_for('.import_feeds') }}">Entries</a> page.
|
|
73
|
+
|
|
74
|
+
Some feeds only include summaries, not the full content; click the
|
|
75
|
+
<i class="bi bi-box-arrow-up-right" aria-label="extenal link"></i> icon
|
|
76
|
+
to go to the original article.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
<h3 id="entry-flags">Entry flags</h3>
|
|
80
|
+
|
|
81
|
+
<p>
|
|
82
|
+
Each entry has a <em>read</em> and an <em>important</em> flag.
|
|
83
|
+
Among others, they are used to filter entries
|
|
84
|
+
on the <a href="{{ url_for('.entries') }}">Entries</a> page.
|
|
85
|
+
|
|
86
|
+
<p>
|
|
87
|
+
You can set the flags by using the toggles below the entry title.
|
|
88
|
+
|
|
89
|
+
<dl>
|
|
90
|
+
|
|
91
|
+
<dt>
|
|
92
|
+
<i class="bi bi-check-lg me-1"></i> mark as read / mark as unread
|
|
93
|
+
<dd>
|
|
94
|
+
Mark the entry as read.
|
|
95
|
+
Hides the entry from the default entries view.
|
|
96
|
+
|
|
97
|
+
<dt>
|
|
98
|
+
<i class="bi bi-star me-1"></i> mark as important / clear important
|
|
99
|
+
<dd>
|
|
100
|
+
Mark the entry as important.
|
|
101
|
+
Important entries are never deleted.
|
|
102
|
+
<small>
|
|
103
|
+
(Currently, entries are never deleted,
|
|
104
|
+
but an upcoming feature will clean up old entries.)
|
|
105
|
+
</small>
|
|
106
|
+
|
|
107
|
+
<dt>
|
|
108
|
+
<i class="bi bi-x-lg me-1"></i> don't care / clear don't care
|
|
109
|
+
<dd>
|
|
110
|
+
Mark the entry as explicitly <em>not</em> important.
|
|
111
|
+
Hides the entry from the default entries view without marking it as read.
|
|
112
|
+
<small>
|
|
113
|
+
(An upcoming feature will allow identifying "noisy" feeds based on this.)
|
|
114
|
+
</small>
|
|
115
|
+
|
|
116
|
+
</dl>
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
<h3 id="enclosures">Enclosures</h3>
|
|
120
|
+
|
|
121
|
+
<p>
|
|
122
|
+
Some entries have external resources you can download,
|
|
123
|
+
for example an audio file of a podcast episode.
|
|
124
|
+
|
|
125
|
+
<p>
|
|
126
|
+
Click the
|
|
127
|
+
<i class="bi bi-file-earmark-music" aria-label="download ..."></i> icon
|
|
128
|
+
to download the first enclosure of an entry, if any.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
{% endblock %}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
{% set help_topic = 'import-export' %}
|
|
1
2
|
{% extends "layout.html" %}
|
|
2
3
|
|
|
3
4
|
{% import "macros.html" as macros %}
|
|
@@ -24,15 +25,16 @@
|
|
|
24
25
|
|
|
25
26
|
{% if parsed_feeds is none and imported_feeds is none %}
|
|
26
27
|
|
|
27
|
-
<p>Upload OPML subscription list:</p>
|
|
28
|
+
<p>Upload an OPML subscription list:</p>
|
|
28
29
|
|
|
29
30
|
<div class="mb-3">
|
|
30
31
|
|
|
31
|
-
<!-- TODO accept -->
|
|
32
32
|
<input
|
|
33
33
|
class="form-control{% if error %} is-invalid {% endif %}"
|
|
34
34
|
type="file"
|
|
35
35
|
name="file"
|
|
36
|
+
accept="application/xml, text/xml, text/x-opml, .xml, .opml"
|
|
37
|
+
required
|
|
36
38
|
>
|
|
37
39
|
{%- if error %}
|
|
38
40
|
<div class="invalid-feedback">
|
|
@@ -52,6 +54,10 @@
|
|
|
52
54
|
|
|
53
55
|
<p>Select feeds to import:</p>
|
|
54
56
|
|
|
57
|
+
<div class="mb-3">
|
|
58
|
+
{{ macros.select_all_none() }}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
55
61
|
{% for feed in parsed_feeds %}
|
|
56
62
|
<div class="form-check mb-3">
|
|
57
63
|
<input
|
|
@@ -79,6 +85,10 @@
|
|
|
79
85
|
</div>
|
|
80
86
|
{% endfor %}
|
|
81
87
|
|
|
88
|
+
<div class="mb-3">
|
|
89
|
+
{{ macros.select_all_none() }}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
82
92
|
<input type="submit" class="btn btn-primary" value="import selected">
|
|
83
93
|
|
|
84
94
|
<a href="javascript:history.back()" class="btn btn-outline-secondary" role="button" >
|
|
@@ -62,12 +62,22 @@
|
|
|
62
62
|
<ul class="navbar-nav mb-0">
|
|
63
63
|
{% block right_hidden_navbar %}
|
|
64
64
|
|
|
65
|
+
<li class="nav-item">
|
|
66
|
+
<a
|
|
67
|
+
href="{{ url_for('reader.help', _anchor=help_topic | default(none)) }}"
|
|
68
|
+
class="btn btn-link nav-link d-flex align-items-center"
|
|
69
|
+
>
|
|
70
|
+
<i class="bi bi-question-lg"></i>
|
|
71
|
+
<span class="d-md-none ms-2" id="theme-text">help</span>
|
|
72
|
+
</a>
|
|
73
|
+
</li>
|
|
74
|
+
|
|
65
75
|
<li class="nav-item dropdown">
|
|
66
76
|
<button
|
|
67
77
|
class="btn btn-link nav-link dropdown-toggle d-flex align-items-center"
|
|
68
78
|
type="button" id="theme"
|
|
69
79
|
data-bs-toggle="dropdown" data-bs-display="static"
|
|
70
|
-
aria-expanded="false"
|
|
80
|
+
aria-expanded="false" aria-label="toggle theme (auto)">
|
|
71
81
|
<i class="bi bi-circle-half theme-icon-active"></i>
|
|
72
82
|
<span class="d-md-none ms-2" id="theme-text">toggle theme</span>
|
|
73
83
|
</button>
|