oldnews 0.0.2__tar.gz → 0.1.0__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.
Files changed (29) hide show
  1. {oldnews-0.0.2 → oldnews-0.1.0}/PKG-INFO +4 -4
  2. {oldnews-0.0.2 → oldnews-0.1.0}/pyproject.toml +1 -1
  3. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/commands/__init__.py +6 -0
  4. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/commands/main.py +21 -0
  5. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/providers/main.py +6 -0
  6. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/screens/main.py +71 -10
  7. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/widgets/article_list.py +22 -4
  8. {oldnews-0.0.2 → oldnews-0.1.0}/README.md +0 -0
  9. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/__init__.py +0 -0
  10. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/__main__.py +0 -0
  11. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/__init__.py +0 -0
  12. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/auth.py +0 -0
  13. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/config.py +0 -0
  14. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/db.py +0 -0
  15. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/last_grab.py +0 -0
  16. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/local_articles.py +0 -0
  17. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/local_folders.py +0 -0
  18. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/local_subscriptions.py +0 -0
  19. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/local_unread.py +0 -0
  20. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/locations.py +0 -0
  21. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/data/navigation_state.py +0 -0
  22. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/oldnews.py +0 -0
  23. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/providers/__init__.py +0 -0
  24. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/py.typed +0 -0
  25. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/screens/__init__.py +0 -0
  26. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/screens/login.py +0 -0
  27. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/widgets/__init__.py +0 -0
  28. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/widgets/article_content.py +0 -0
  29. {oldnews-0.0.2 → oldnews-0.1.0}/src/oldnews/widgets/navigation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oldnews
3
- Version: 0.0.2
3
+ Version: 0.1.0
4
4
  Summary: A terminal-based client for TheOldReader
5
5
  Keywords: atom,client,Google Reader,RSS,TheOldReader,terminal,news-reader,news
6
6
  Author: Dave Pearson
@@ -22,12 +22,12 @@ Requires-Dist: textual-enhanced>=1.2.0
22
22
  Requires-Dist: typedal>=4.2.2
23
23
  Requires-Dist: xdg-base-dirs>=6.0.2
24
24
  Requires-Python: >=3.11
25
- Project-URL: Discussions, https://github.com/davep/oldnews/discussions
26
- Project-URL: Documentation, https://oldnews.davep.dev/
27
25
  Project-URL: Homepage, https://github.com/davep/oldnews
28
- Project-URL: Issues, https://github.com/davep/oldnews/issues
29
26
  Project-URL: Repository, https://github.com/davep/oldnews
27
+ Project-URL: Documentation, https://oldnews.davep.dev/
30
28
  Project-URL: Source, https://github.com/davep/oldnews
29
+ Project-URL: Issues, https://github.com/davep/oldnews/issues
30
+ Project-URL: Discussions, https://github.com/davep/oldnews/discussions
31
31
  Description-Content-Type: text/markdown
32
32
 
33
33
  # OldNews - A TheOldReader client for the terminal
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oldnews"
3
- version = "0.0.2"
3
+ version = "0.1.0"
4
4
  description = "A terminal-based client for TheOldReader"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -4,8 +4,11 @@
4
4
  # Local imports.
5
5
  from .main import (
6
6
  Escape,
7
+ MarkAllRead,
8
+ Next,
7
9
  NextUnread,
8
10
  OpenArticle,
11
+ Previous,
9
12
  PreviousUnread,
10
13
  RefreshFromTheOldReader,
11
14
  ToggleShowAll,
@@ -15,8 +18,11 @@ from .main import (
15
18
  # Exports.
16
19
  __all__ = [
17
20
  "Escape",
21
+ "MarkAllRead",
22
+ "Next",
18
23
  "NextUnread",
19
24
  "OpenArticle",
25
+ "Previous",
20
26
  "PreviousUnread",
21
27
  "RefreshFromTheOldReader",
22
28
  "ToggleShowAll",
@@ -35,6 +35,13 @@ class NextUnread(Command):
35
35
  BINDING_KEY = "n"
36
36
 
37
37
 
38
+ ##############################################################################
39
+ class Next(Command):
40
+ """Navigate to the next article regardless of read status"""
41
+
42
+ BINDING_KEY = "N"
43
+
44
+
38
45
  ##############################################################################
39
46
  class PreviousUnread(Command):
40
47
  """Navigate to the previous unread article in the currently-selected category"""
@@ -42,6 +49,13 @@ class PreviousUnread(Command):
42
49
  BINDING_KEY = "p"
43
50
 
44
51
 
52
+ ##############################################################################
53
+ class Previous(Command):
54
+ """Navigate to the next article regardless of read status"""
55
+
56
+ BINDING_KEY = "P"
57
+
58
+
45
59
  ##############################################################################
46
60
  class OpenArticle(Command):
47
61
  """Open the current article in the web browser"""
@@ -49,4 +63,11 @@ class OpenArticle(Command):
49
63
  BINDING_KEY = "o"
50
64
 
51
65
 
66
+ ##############################################################################
67
+ class MarkAllRead(Command):
68
+ """Mark all unread articles in the current category as read"""
69
+
70
+ BINDING_KEY = "R"
71
+
72
+
52
73
  ### main.py ends here
@@ -14,8 +14,11 @@ from textual_enhanced.commands import (
14
14
  # Local imports.
15
15
  from ..commands import (
16
16
  Escape,
17
+ MarkAllRead,
18
+ Next,
17
19
  NextUnread,
18
20
  OpenArticle,
21
+ Previous,
19
22
  PreviousUnread,
20
23
  RefreshFromTheOldReader,
21
24
  ToggleShowAll,
@@ -33,9 +36,12 @@ class MainCommands(CommandsProvider):
33
36
  The commands for the command palette.
34
37
  """
35
38
  yield Escape()
39
+ yield from self.maybe(Next)
36
40
  yield from self.maybe(NextUnread)
41
+ yield from self.maybe(Previous)
37
42
  yield from self.maybe(PreviousUnread)
38
43
  yield from self.maybe(OpenArticle)
44
+ yield from self.maybe(MarkAllRead)
39
45
  yield ToggleShowAll()
40
46
  yield RefreshFromTheOldReader()
41
47
  yield ChangeTheme()
@@ -15,6 +15,7 @@ from oldas import (
15
15
  Folder,
16
16
  Folders,
17
17
  Session,
18
+ State,
18
19
  Subscription,
19
20
  Subscriptions,
20
21
  )
@@ -31,6 +32,7 @@ from textual.widgets import Footer, Header
31
32
  ##############################################################################
32
33
  # Textual enhanced imports.
33
34
  from textual_enhanced.commands import ChangeTheme, Command, Help, Quit
35
+ from textual_enhanced.dialogs import Confirm
34
36
  from textual_enhanced.screen import EnhancedScreen
35
37
 
36
38
  ##############################################################################
@@ -38,8 +40,11 @@ from textual_enhanced.screen import EnhancedScreen
38
40
  from .. import __version__
39
41
  from ..commands import (
40
42
  Escape,
43
+ MarkAllRead,
44
+ Next,
41
45
  NextUnread,
42
46
  OpenArticle,
47
+ Previous,
43
48
  PreviousUnread,
44
49
  RefreshFromTheOldReader,
45
50
  ToggleShowAll,
@@ -134,7 +139,10 @@ class Main(EnhancedScreen[None]):
134
139
  RefreshFromTheOldReader,
135
140
  # Everything else.
136
141
  Escape,
142
+ MarkAllRead,
143
+ Next,
137
144
  NextUnread,
145
+ Previous,
138
146
  PreviousUnread,
139
147
  OpenArticle,
140
148
  ChangeTheme,
@@ -230,7 +238,13 @@ class Main(EnhancedScreen[None]):
230
238
  return True
231
239
  if action == OpenArticle.action_name():
232
240
  return self.article is not None
233
- if action in (NextUnread.action_name(), PreviousUnread.action_name()):
241
+ if action in (Next.action_name(), Previous.action_name()):
242
+ return self.articles is not None
243
+ if action in (
244
+ NextUnread.action_name(),
245
+ PreviousUnread.action_name(),
246
+ MarkAllRead.action_name(),
247
+ ):
234
248
  return self.articles is not None and any(
235
249
  article.is_unread for article in self.articles
236
250
  )
@@ -336,12 +350,10 @@ class Main(EnhancedScreen[None]):
336
350
  self.post_message(self.SubTitle("Comparing against locally-read articles"))
337
351
  local_unread_articles = set(get_unread_article_ids())
338
352
  if mark_as_read := local_unread_articles - remote_unread_articles:
339
- self.post_message(
340
- self.SubTitle(
341
- f"Articles found read elsewhere on TheOldReader: {len(mark_as_read)}"
342
- )
343
- )
344
353
  locally_mark_article_ids_read(mark_as_read)
354
+ self.notify(
355
+ f"Articles found read elsewhere on TheOldReader: {len(mark_as_read)}"
356
+ )
345
357
 
346
358
  @on(RefreshFromTheOldReader)
347
359
  @work(exclusive=True)
@@ -459,19 +471,33 @@ class Main(EnhancedScreen[None]):
459
471
  elif self.focused is self.query_one(Navigation):
460
472
  self.app.exit()
461
473
 
474
+ def action_next_command(self) -> None:
475
+ """Go to the next article in the currently-viewed category."""
476
+ if self.article is None:
477
+ self.query_one(ArticleList).highlight_next_article()
478
+ else:
479
+ self.query_one(ArticleList).select_next_article()
480
+
481
+ def action_previous_command(self) -> None:
482
+ """Go to the previous article in the currently-viewed category."""
483
+ if self.article is None:
484
+ self.query_one(ArticleList).highlight_previous_article()
485
+ else:
486
+ self.query_one(ArticleList).select_previous_article()
487
+
462
488
  def action_next_unread_command(self) -> None:
463
489
  """Go to the next unread article in the currently-viewed category."""
464
490
  if self.article is None:
465
- self.query_one(ArticleList).highlight_next_unread()
491
+ self.query_one(ArticleList).highlight_next_unread_article()
466
492
  else:
467
- self.query_one(ArticleList).select_next_unread()
493
+ self.query_one(ArticleList).select_next_unread_article()
468
494
 
469
495
  def action_previous_unread_command(self) -> None:
470
496
  """Go to the previous unread article in the currently-viewed category"""
471
497
  if self.article is None:
472
- self.query_one(ArticleList).highlight_previous_unread()
498
+ self.query_one(ArticleList).highlight_previous_unread_article()
473
499
  else:
474
- self.query_one(ArticleList).select_previous_unread()
500
+ self.query_one(ArticleList).select_previous_unread_article()
475
501
 
476
502
  def action_open_article_command(self) -> None:
477
503
  """Open the current article in a web browser."""
@@ -485,5 +511,40 @@ class Main(EnhancedScreen[None]):
485
511
  title="Can't visit",
486
512
  )
487
513
 
514
+ @work
515
+ async def action_mark_all_read_command(self) -> None:
516
+ """Mark all unread articles in the current category as read."""
517
+ if (current_category := self.query_one(Navigation).current_category) is None:
518
+ return
519
+ if not (
520
+ ids_to_mark_read := [
521
+ article.id for article in self.articles if article.is_unread
522
+ ]
523
+ ):
524
+ return
525
+ category_description = f"{current_category.__class__.__name__.lower()} '{current_category.name if isinstance(current_category, Folder) else current_category.title}'"
526
+ plural = "s" if len(ids_to_mark_read) > 1 else ""
527
+ if await self.app.push_screen_wait(
528
+ Confirm(
529
+ "Mark all read",
530
+ f"Are you sure you want to mark all unread articles in the {category_description} as read?\n\n"
531
+ f"This will mark {len(ids_to_mark_read)} article{plural} as read.",
532
+ )
533
+ ):
534
+ if await self._session.add_tag(ids_to_mark_read, State.READ):
535
+ locally_mark_article_ids_read(ids_to_mark_read)
536
+ self.post_message(
537
+ self.NewUnread(get_local_unread(self.folders, self.subscriptions))
538
+ )
539
+ self._refresh_article_list()
540
+ self.notify(
541
+ f"{len(ids_to_mark_read)} article{plural} marked read for {category_description}"
542
+ )
543
+ else:
544
+ self.notify(
545
+ "Failed to mark as read on TheOldReader",
546
+ severity="error",
547
+ )
548
+
488
549
 
489
550
  ### main.py ends here
@@ -185,20 +185,38 @@ class ArticleList(EnhancedOptionList):
185
185
  self.notify("No more unread articles")
186
186
  return False
187
187
 
188
- def highlight_next_unread(self) -> None:
188
+ def highlight_next_article(self) -> None:
189
+ """Highlight the next article in the list."""
190
+ self.call_later(self.run_action, "cursor_down")
191
+
192
+ def highlight_previous_article(self) -> None:
193
+ """Highlight the previous article in the list."""
194
+ self.call_later(self.run_action, "cursor_up")
195
+
196
+ def highlight_next_unread_article(self) -> None:
189
197
  """Highlight the next unread article in the list."""
190
198
  self._highlight_unread("next")
191
199
 
192
- def highlight_previous_unread(self) -> None:
200
+ def highlight_previous_unread_article(self) -> None:
193
201
  """Highlight the previous unread article in the list."""
194
202
  self._highlight_unread("previous")
195
203
 
196
- def select_next_unread(self) -> None:
204
+ def select_next_article(self) -> None:
205
+ """Select the next article in the list."""
206
+ self.call_later(self.run_action, "cursor_down")
207
+ self.call_later(self.run_action, "select")
208
+
209
+ def select_previous_article(self) -> None:
210
+ """Select the previous article in the list."""
211
+ self.call_later(self.run_action, "cursor_up")
212
+ self.call_later(self.run_action, "select")
213
+
214
+ def select_next_unread_article(self) -> None:
197
215
  """Select the next unread article in the list."""
198
216
  if self._highlight_unread("next"):
199
217
  self.call_later(self.run_action, "select")
200
218
 
201
- def select_previous_unread(self) -> None:
219
+ def select_previous_unread_article(self) -> None:
202
220
  """Select the next unread article in the list."""
203
221
  if self._highlight_unread("previous"):
204
222
  self.call_later(self.run_action, "select")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes