oldnews 0.7.0__py3-none-any.whl → 0.8.0__py3-none-any.whl
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.
- oldnews/screens/main.py +20 -14
- oldnews/screens/process_subscription.py +87 -0
- oldnews/sync.py +21 -15
- oldnews/widgets/_next_matching_option.py +52 -0
- oldnews/widgets/article_list.py +12 -14
- oldnews/widgets/navigation.py +20 -12
- {oldnews-0.7.0.dist-info → oldnews-0.8.0.dist-info}/METADATA +2 -1
- {oldnews-0.7.0.dist-info → oldnews-0.8.0.dist-info}/RECORD +10 -9
- {oldnews-0.7.0.dist-info → oldnews-0.8.0.dist-info}/WHEEL +1 -1
- oldnews/widgets/_after_highlight.py +0 -53
- {oldnews-0.7.0.dist-info → oldnews-0.8.0.dist-info}/entry_points.txt +0 -0
oldnews/screens/main.py
CHANGED
|
@@ -6,6 +6,10 @@ from dataclasses import dataclass
|
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
7
|
from webbrowser import open as open_url
|
|
8
8
|
|
|
9
|
+
##############################################################################
|
|
10
|
+
# BagOfStuff imports.
|
|
11
|
+
from bagofstuff.pipe import Pipe
|
|
12
|
+
|
|
9
13
|
##############################################################################
|
|
10
14
|
# OldAs imports.
|
|
11
15
|
from oldas import (
|
|
@@ -79,11 +83,12 @@ from ..data import (
|
|
|
79
83
|
update_configuration,
|
|
80
84
|
)
|
|
81
85
|
from ..providers import MainCommands
|
|
82
|
-
from ..sync import
|
|
86
|
+
from ..sync import TheOldReaderSync
|
|
83
87
|
from ..widgets import ArticleContent, ArticleList, Navigation
|
|
84
88
|
from .folder_input import FolderInput
|
|
85
89
|
from .information_display import InformationDisplay
|
|
86
90
|
from .new_subscription import NewSubscription
|
|
91
|
+
from .process_subscription import ProcessSubscription
|
|
87
92
|
|
|
88
93
|
|
|
89
94
|
##############################################################################
|
|
@@ -402,17 +407,19 @@ class Main(EnhancedScreen[None]):
|
|
|
402
407
|
@work(exclusive=True)
|
|
403
408
|
async def action_refresh_from_the_old_reader_command(self) -> None:
|
|
404
409
|
"""Load the main data from TheOldReader."""
|
|
405
|
-
await
|
|
410
|
+
await TheOldReaderSync(
|
|
406
411
|
self._session,
|
|
407
|
-
on_new_step=
|
|
408
|
-
on_new_result=
|
|
409
|
-
on_new_folders=
|
|
410
|
-
on_new_subscriptions=
|
|
411
|
-
self.NewSubscriptions
|
|
412
|
+
on_new_step=Pipe[str, bool](self.SubTitle, self.post_message),
|
|
413
|
+
on_new_result=self.notify,
|
|
414
|
+
on_new_folders=Pipe[Folders, bool](self.NewFolders, self.post_message),
|
|
415
|
+
on_new_subscriptions=Pipe[Subscriptions, bool](
|
|
416
|
+
self.NewSubscriptions, self.post_message
|
|
417
|
+
),
|
|
418
|
+
on_new_unread=Pipe[LocalUnread, bool](self.NewUnread, self.post_message),
|
|
419
|
+
on_sync_finished=Pipe[Pipe.Nullary, bool](
|
|
420
|
+
self.SyncFinished, self.post_message
|
|
412
421
|
),
|
|
413
|
-
|
|
414
|
-
on_sync_finished=lambda: self.post_message(self.SyncFinished()),
|
|
415
|
-
).refresh()
|
|
422
|
+
).sync()
|
|
416
423
|
|
|
417
424
|
@on(Navigation.CategorySelected)
|
|
418
425
|
def _handle_navigaion_selection(self, message: Navigation.CategorySelected) -> None:
|
|
@@ -624,11 +631,10 @@ class Main(EnhancedScreen[None]):
|
|
|
624
631
|
if subscription := await self.app.push_screen_wait(
|
|
625
632
|
NewSubscription(self.folders)
|
|
626
633
|
):
|
|
627
|
-
self.notify(
|
|
628
|
-
subscription.feed, title="Subscription request sent to TheOldReader..."
|
|
629
|
-
)
|
|
630
634
|
if (
|
|
631
|
-
result := await
|
|
635
|
+
result := await self.app.push_screen_wait(
|
|
636
|
+
ProcessSubscription(self._session, subscription)
|
|
637
|
+
)
|
|
632
638
|
).failed:
|
|
633
639
|
self.notify(
|
|
634
640
|
result.error or "TheOldReader did not give a reason",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Provides a dialog for waiting for TheOldReader to add a subscription."""
|
|
2
|
+
|
|
3
|
+
##############################################################################
|
|
4
|
+
# OldAS imports.
|
|
5
|
+
from oldas import Session, Subscriptions
|
|
6
|
+
from oldas.subscriptions import SubscribeResult
|
|
7
|
+
|
|
8
|
+
##############################################################################
|
|
9
|
+
# Textual imports.
|
|
10
|
+
from textual import work
|
|
11
|
+
from textual.app import ComposeResult
|
|
12
|
+
from textual.containers import Center, Vertical
|
|
13
|
+
from textual.screen import ModalScreen
|
|
14
|
+
from textual.widgets import Label, LoadingIndicator
|
|
15
|
+
|
|
16
|
+
##############################################################################
|
|
17
|
+
# Local data.
|
|
18
|
+
from .new_subscription import NewSubscriptionData
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
##############################################################################
|
|
22
|
+
class ProcessSubscription(ModalScreen[SubscribeResult]):
|
|
23
|
+
"""Dialog for waiting for TheOldReader to process a subscription."""
|
|
24
|
+
|
|
25
|
+
CSS = """
|
|
26
|
+
ProcessSubscription {
|
|
27
|
+
align: center middle;
|
|
28
|
+
|
|
29
|
+
&> Vertical {
|
|
30
|
+
padding: 1 2;
|
|
31
|
+
width: auto;
|
|
32
|
+
height: auto;
|
|
33
|
+
min-width: 60%;
|
|
34
|
+
max-width: 90%;
|
|
35
|
+
background: $panel;
|
|
36
|
+
border: panel $border;
|
|
37
|
+
LoadingIndicator {
|
|
38
|
+
margin-top: 1;
|
|
39
|
+
}
|
|
40
|
+
Center {
|
|
41
|
+
width: 100%;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, session: Session, new_subscription: NewSubscriptionData) -> None:
|
|
48
|
+
"""Initialise the subscription processing dialog object.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
session: The ToR API session object.
|
|
52
|
+
new_subscription: The new subscription.
|
|
53
|
+
"""
|
|
54
|
+
self._session = session
|
|
55
|
+
"""The API session object."""
|
|
56
|
+
self._feed = new_subscription.feed
|
|
57
|
+
"""The feed to subscribe to."""
|
|
58
|
+
self._folder = new_subscription.folder
|
|
59
|
+
"""The folder to place the subscription in."""
|
|
60
|
+
super().__init__()
|
|
61
|
+
|
|
62
|
+
def compose(self) -> ComposeResult:
|
|
63
|
+
"""Compose the content of the screen."""
|
|
64
|
+
with Vertical() as dialog:
|
|
65
|
+
dialog.border_title = "Waiting for TheOldReader to add the feed..."
|
|
66
|
+
yield Label(
|
|
67
|
+
f"Subscribing to {self._feed}"
|
|
68
|
+
+ (f" and adding it to {self._folder}" if self._folder else "")
|
|
69
|
+
+ ".\n\n"
|
|
70
|
+
"TheOldReader can take a short while to scan and add a feed. Please wait...",
|
|
71
|
+
shrink=True,
|
|
72
|
+
markup=False,
|
|
73
|
+
)
|
|
74
|
+
with Center():
|
|
75
|
+
yield LoadingIndicator()
|
|
76
|
+
|
|
77
|
+
@work
|
|
78
|
+
async def _request_subscription(self) -> None:
|
|
79
|
+
"""Process the request."""
|
|
80
|
+
self.dismiss(await Subscriptions.add(self._session, self._feed))
|
|
81
|
+
|
|
82
|
+
def on_mount(self) -> None:
|
|
83
|
+
"""Start the work once the DOM is ready."""
|
|
84
|
+
self._request_subscription()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
### process_subscription.py ends here
|
oldnews/sync.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Python imports.
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from datetime import datetime, timedelta, timezone
|
|
7
|
-
from typing import Any, AsyncIterator, Callable, Iterable
|
|
7
|
+
from typing import Any, AsyncIterator, Callable, Final, Iterable
|
|
8
8
|
|
|
9
9
|
##############################################################################
|
|
10
10
|
# OldAS imports.
|
|
@@ -41,10 +41,14 @@ type Callback = Callable[[], Any] | None
|
|
|
41
41
|
type CallbackWith[T] = Callable[[T], Any] | None
|
|
42
42
|
"""Type of callback with a single argument."""
|
|
43
43
|
|
|
44
|
+
##############################################################################
|
|
45
|
+
BATCH_SIZE: Final[int] = 10
|
|
46
|
+
"""Batch size for downloading articles."""
|
|
47
|
+
|
|
44
48
|
|
|
45
49
|
##############################################################################
|
|
46
50
|
@dataclass
|
|
47
|
-
class
|
|
51
|
+
class TheOldReaderSync:
|
|
48
52
|
"""Class that handles syncing data from TheOldReader."""
|
|
49
53
|
|
|
50
54
|
session: Session
|
|
@@ -111,11 +115,11 @@ class ToRSync:
|
|
|
111
115
|
# TODO: Right now I'm saving articles one at a time; perhaps I
|
|
112
116
|
# should save them in small batches? This would be simple enough
|
|
113
117
|
# -- perhaps same them in batches the same size as the buffer
|
|
114
|
-
# window I'm using right now (currently
|
|
115
|
-
# ToR).
|
|
118
|
+
# window I'm using right now (currently BATCH_SIZE articles per
|
|
119
|
+
# trip to ToR).
|
|
116
120
|
save_local_articles(Articles([article]))
|
|
117
121
|
loaded += 1
|
|
118
|
-
if (loaded %
|
|
122
|
+
if (loaded % BATCH_SIZE) == 0:
|
|
119
123
|
self._step(f"{description}: {loaded}", log=False)
|
|
120
124
|
return loaded
|
|
121
125
|
|
|
@@ -147,7 +151,9 @@ class ToRSync:
|
|
|
147
151
|
)
|
|
148
152
|
for subscription in subscriptions:
|
|
149
153
|
if loaded := await self._download(
|
|
150
|
-
Articles.stream_new_since(
|
|
154
|
+
Articles.stream_new_since(
|
|
155
|
+
self.session, cutoff, subscription, n=BATCH_SIZE
|
|
156
|
+
),
|
|
151
157
|
f"Downloading article backlog for {subscription.title}",
|
|
152
158
|
):
|
|
153
159
|
self._result(
|
|
@@ -192,7 +198,7 @@ class ToRSync:
|
|
|
192
198
|
new_grab - timedelta(days=load_configuration().local_history)
|
|
193
199
|
)
|
|
194
200
|
if loaded := await self._download(
|
|
195
|
-
Articles.stream_new_since(self.session, last_grabbed, n=
|
|
201
|
+
Articles.stream_new_since(self.session, last_grabbed, n=BATCH_SIZE),
|
|
196
202
|
"Downloading articles from TheOldReader",
|
|
197
203
|
):
|
|
198
204
|
self._result(f"Articles downloaded: {loaded}")
|
|
@@ -264,17 +270,17 @@ class ToRSync:
|
|
|
264
270
|
def _get_unread_counts(
|
|
265
271
|
self, folders: Folders, subscriptions: Subscriptions
|
|
266
272
|
) -> None:
|
|
267
|
-
"""Get the updated unread counts.
|
|
268
|
-
unread = get_local_unread(folders, subscriptions)
|
|
269
|
-
if self.on_new_unread:
|
|
270
|
-
self.on_new_unread(unread)
|
|
271
|
-
|
|
272
|
-
async def refresh(self) -> None:
|
|
273
|
-
"""Refresh the data from TheOldReader.
|
|
273
|
+
"""Get the updated unread counts.
|
|
274
274
|
|
|
275
275
|
Args:
|
|
276
|
-
|
|
276
|
+
folders: The folders to get the counts for.
|
|
277
|
+
subscriptions: The subscriptions to get the counts for.
|
|
277
278
|
"""
|
|
279
|
+
if self.on_new_unread:
|
|
280
|
+
self.on_new_unread(get_local_unread(folders, subscriptions))
|
|
281
|
+
|
|
282
|
+
async def sync(self) -> None:
|
|
283
|
+
"""Sync the data from TheOldReader."""
|
|
278
284
|
folders = await self._get_folders()
|
|
279
285
|
original_subscriptions, subscriptions = await self._get_subscriptions()
|
|
280
286
|
await self._get_new_articles()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Support code for OptionLists that want to find things."""
|
|
2
|
+
|
|
3
|
+
##############################################################################
|
|
4
|
+
# Python imports.
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
##############################################################################
|
|
8
|
+
# BagOfStuff imports.
|
|
9
|
+
from bagofstuff.itertools import Direction, starting_at
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
##############################################################################
|
|
13
|
+
def next_matching_option[T](
|
|
14
|
+
options: list[T],
|
|
15
|
+
current_highlight: int | None,
|
|
16
|
+
direction: Direction,
|
|
17
|
+
matching: Callable[[T], bool] | None = None,
|
|
18
|
+
) -> T | None:
|
|
19
|
+
"""Return a list of `OptionList` options after the highlight.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
options: The options from the given option list.
|
|
23
|
+
current_highlight: The current highlighted option.
|
|
24
|
+
direction: The direction to work in.
|
|
25
|
+
matching: Optional filter to apply to the list.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The next matching option, or `None` if there isn't one.
|
|
29
|
+
|
|
30
|
+
Notes:
|
|
31
|
+
If there is no highlight, we default at position 0.
|
|
32
|
+
"""
|
|
33
|
+
matching = matching or (lambda _: True)
|
|
34
|
+
if current_highlight is None:
|
|
35
|
+
current_highlight = 0
|
|
36
|
+
else:
|
|
37
|
+
current_highlight += 1 if direction == "forward" else -1
|
|
38
|
+
return next(
|
|
39
|
+
(
|
|
40
|
+
option
|
|
41
|
+
for option in starting_at(options, current_highlight, direction)
|
|
42
|
+
if matching(option)
|
|
43
|
+
),
|
|
44
|
+
None,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
##############################################################################
|
|
49
|
+
# Re-export the direction type.
|
|
50
|
+
__all__ = ["Direction"]
|
|
51
|
+
|
|
52
|
+
### _next_matching_option.py ends here
|
oldnews/widgets/article_list.py
CHANGED
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
##############################################################################
|
|
8
8
|
# Python imports.
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
+
from operator import attrgetter
|
|
10
11
|
from typing import cast
|
|
11
12
|
|
|
12
13
|
##############################################################################
|
|
@@ -32,7 +33,7 @@ from textual_enhanced.widgets import EnhancedOptionList
|
|
|
32
33
|
|
|
33
34
|
##############################################################################
|
|
34
35
|
# Local imports.
|
|
35
|
-
from .
|
|
36
|
+
from ._next_matching_option import Direction, next_matching_option
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
##############################################################################
|
|
@@ -142,7 +143,7 @@ class ArticleList(EnhancedOptionList):
|
|
|
142
143
|
assert isinstance(message.option, ArticleView)
|
|
143
144
|
self.post_message(self.ViewArticle(message.option.article))
|
|
144
145
|
|
|
145
|
-
def _highlight_unread(self, direction:
|
|
146
|
+
def _highlight_unread(self, direction: Direction) -> bool:
|
|
146
147
|
"""Highlight the next unread article, if there is one.
|
|
147
148
|
|
|
148
149
|
Args:
|
|
@@ -152,14 +153,11 @@ class ArticleList(EnhancedOptionList):
|
|
|
152
153
|
`True` if an unread article was found and highlighted, `False`
|
|
153
154
|
if not.
|
|
154
155
|
"""
|
|
155
|
-
if next_hit :=
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
lambda article_view: article_view.article.is_unread,
|
|
161
|
-
),
|
|
162
|
-
None,
|
|
156
|
+
if next_hit := next_matching_option(
|
|
157
|
+
cast(list[ArticleView], self.options),
|
|
158
|
+
self.highlighted,
|
|
159
|
+
direction,
|
|
160
|
+
attrgetter("article.is_unread"),
|
|
163
161
|
):
|
|
164
162
|
if next_hit.id is not None:
|
|
165
163
|
self.highlighted = self.get_option_index(next_hit.id)
|
|
@@ -177,11 +175,11 @@ class ArticleList(EnhancedOptionList):
|
|
|
177
175
|
|
|
178
176
|
def highlight_next_unread_article(self) -> None:
|
|
179
177
|
"""Highlight the next unread article in the list."""
|
|
180
|
-
self._highlight_unread("
|
|
178
|
+
self._highlight_unread("forward")
|
|
181
179
|
|
|
182
180
|
def highlight_previous_unread_article(self) -> None:
|
|
183
181
|
"""Highlight the previous unread article in the list."""
|
|
184
|
-
self._highlight_unread("
|
|
182
|
+
self._highlight_unread("backward")
|
|
185
183
|
|
|
186
184
|
def select_next_article(self) -> None:
|
|
187
185
|
"""Select the next article in the list."""
|
|
@@ -195,12 +193,12 @@ class ArticleList(EnhancedOptionList):
|
|
|
195
193
|
|
|
196
194
|
def select_next_unread_article(self) -> None:
|
|
197
195
|
"""Select the next unread article in the list."""
|
|
198
|
-
if self._highlight_unread("
|
|
196
|
+
if self._highlight_unread("forward"):
|
|
199
197
|
self.call_later(self.run_action, "select")
|
|
200
198
|
|
|
201
199
|
def select_previous_unread_article(self) -> None:
|
|
202
200
|
"""Select the next unread article in the list."""
|
|
203
|
-
if self._highlight_unread("
|
|
201
|
+
if self._highlight_unread("backward"):
|
|
204
202
|
self.call_later(self.run_action, "select")
|
|
205
203
|
|
|
206
204
|
|
oldnews/widgets/navigation.py
CHANGED
|
@@ -35,7 +35,7 @@ from textual_enhanced.widgets import EnhancedOptionList
|
|
|
35
35
|
##############################################################################
|
|
36
36
|
# Local imports.
|
|
37
37
|
from ..data import LocalUnread, get_navigation_state, save_navigation_state
|
|
38
|
-
from .
|
|
38
|
+
from ._next_matching_option import Direction, next_matching_option
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
##############################################################################
|
|
@@ -328,7 +328,18 @@ class Navigation(EnhancedOptionList):
|
|
|
328
328
|
return current
|
|
329
329
|
return None
|
|
330
330
|
|
|
331
|
-
def
|
|
331
|
+
def _contains_unread(self, category: FolderView | SubscriptionView) -> bool:
|
|
332
|
+
"""Does the given folder or subscription have unread items?
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
category: The folder or subscription to check.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
`True` if there are unread items, `False` if not.
|
|
339
|
+
"""
|
|
340
|
+
return bool(category.id and self.unread.get(category.id))
|
|
341
|
+
|
|
342
|
+
def _highlight_unread(self, direction: Direction) -> bool:
|
|
332
343
|
"""Highlight the next category with unread articles, if there is one.
|
|
333
344
|
|
|
334
345
|
Args:
|
|
@@ -338,14 +349,11 @@ class Navigation(EnhancedOptionList):
|
|
|
338
349
|
`True` if an unread category was found and highlighted, `False`
|
|
339
350
|
if not.
|
|
340
351
|
"""
|
|
341
|
-
if next_hit :=
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
lambda category: bool(category.id and self.unread.get(category.id)),
|
|
347
|
-
),
|
|
348
|
-
None,
|
|
352
|
+
if next_hit := next_matching_option(
|
|
353
|
+
cast(list[FolderView | SubscriptionView], self.options),
|
|
354
|
+
self.highlighted,
|
|
355
|
+
direction,
|
|
356
|
+
self._contains_unread,
|
|
349
357
|
):
|
|
350
358
|
if next_hit.id is not None:
|
|
351
359
|
self.highlighted = self.get_option_index(next_hit.id)
|
|
@@ -355,11 +363,11 @@ class Navigation(EnhancedOptionList):
|
|
|
355
363
|
|
|
356
364
|
def highlight_next_unread_category(self) -> None:
|
|
357
365
|
"""Highlight the next unread category."""
|
|
358
|
-
self._highlight_unread("
|
|
366
|
+
self._highlight_unread("forward")
|
|
359
367
|
|
|
360
368
|
def highlight_previous_unread_category(self) -> None:
|
|
361
369
|
"""Highlight the previous unread category."""
|
|
362
|
-
self._highlight_unread("
|
|
370
|
+
self._highlight_unread("backward")
|
|
363
371
|
|
|
364
372
|
|
|
365
373
|
### navigation.py ends here
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oldnews
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Dist: bagofstuff>=0.1.0
|
|
17
18
|
Requires-Dist: html-to-markdown>=2.15.0
|
|
18
19
|
Requires-Dist: oldas>=0.10.0
|
|
19
20
|
Requires-Dist: textual>=6.3.0
|
|
@@ -25,15 +25,16 @@ oldnews/screens/__init__.py,sha256=-Iif16T316dnZkgJjX1YE7e4v7FxI0k_V08qd5za0t8,3
|
|
|
25
25
|
oldnews/screens/folder_input.py,sha256=i6KeRVD1Ucfg2T6VJmzdKKnUHf6qo_1ka0ZLlNMuabA,1869
|
|
26
26
|
oldnews/screens/information_display.py,sha256=-qG1BJxnhF9HtaP8d_HIuRzwMlAwl5raCLIAFpIDODg,2543
|
|
27
27
|
oldnews/screens/login.py,sha256=a2hUERxpKYg7GQIYlpyDgHTAUEKmsANjwHZx1r-gXSw,3377
|
|
28
|
-
oldnews/screens/main.py,sha256=
|
|
28
|
+
oldnews/screens/main.py,sha256=lV62hmWbxn3Iq90gcGyEmX8X2zM7fQUvZVVo-8EcFHY,28766
|
|
29
29
|
oldnews/screens/new_subscription.py,sha256=bk01RXWu3kvJbtm88o1mrYpll73Op3PWWl7ggIH29kk,4011
|
|
30
|
-
oldnews/
|
|
30
|
+
oldnews/screens/process_subscription.py,sha256=8Z7aJLx-KkItaCQXfgf44cOQ83AZPlRJcMxFDuaHk54,2891
|
|
31
|
+
oldnews/sync.py,sha256=iZbVniwhTTvZmLBGvwviG3wV-lyOBXEPQpm2xgiTeo8,11150
|
|
31
32
|
oldnews/widgets/__init__.py,sha256=5VSjKswHxv2W8g0O-LWlUFS-gy7iQrIM8PGvSBhDNLU,438
|
|
32
|
-
oldnews/widgets/
|
|
33
|
+
oldnews/widgets/_next_matching_option.py,sha256=rKGyIcj_wUcGZEeQJ1QAJmVke1XRsDpNRy9GS7PZEYo,1600
|
|
33
34
|
oldnews/widgets/article_content.py,sha256=pv8zobd3eTsALHYmP854dd_enqy32QXGoWXEILYnWfA,3553
|
|
34
|
-
oldnews/widgets/article_list.py,sha256=
|
|
35
|
-
oldnews/widgets/navigation.py,sha256=
|
|
36
|
-
oldnews-0.
|
|
37
|
-
oldnews-0.
|
|
38
|
-
oldnews-0.
|
|
39
|
-
oldnews-0.
|
|
35
|
+
oldnews/widgets/article_list.py,sha256=586r4LLeQbNhamO4pEe0cj_nHDvctPmJTmTw4amHoEM,7372
|
|
36
|
+
oldnews/widgets/navigation.py,sha256=IWlET89rcXw99sNX2at1Zi76EKVADMfm-MzaG_XfCl4,12698
|
|
37
|
+
oldnews-0.8.0.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
38
|
+
oldnews-0.8.0.dist-info/entry_points.txt,sha256=FxY6Y4IsHZubhtdd0QJG3p2kCTcXe0e-Ib_AW0qkotE,51
|
|
39
|
+
oldnews-0.8.0.dist-info/METADATA,sha256=_9fxAFDTm7UlU8AHPV4wkRHdtIi-TTpS9pRkOwC5byQ,2951
|
|
40
|
+
oldnews-0.8.0.dist-info/RECORD,,
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"""Support code for OptionLists that want to find things."""
|
|
2
|
-
|
|
3
|
-
##############################################################################
|
|
4
|
-
# Python imports.
|
|
5
|
-
from typing import Callable, Iterator, Literal
|
|
6
|
-
|
|
7
|
-
##############################################################################
|
|
8
|
-
# Textual imports.
|
|
9
|
-
from textual.widgets import OptionList
|
|
10
|
-
|
|
11
|
-
##############################################################################
|
|
12
|
-
type HighlightDirection = Literal["next", "previous"]
|
|
13
|
-
"""Type of a unread search direction."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
##############################################################################
|
|
17
|
-
def options_after_highlight[T](
|
|
18
|
-
option_list: OptionList,
|
|
19
|
-
options: list[T],
|
|
20
|
-
direction: HighlightDirection,
|
|
21
|
-
option_filter: Callable[[T], bool] | None = None,
|
|
22
|
-
) -> Iterator[T]:
|
|
23
|
-
"""Return a list of `OptionList` options after the highlight.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
option_list: The `OptionList` to work against.
|
|
27
|
-
options: The options from the given option list.
|
|
28
|
-
direction: The direction to consider as 'after'.
|
|
29
|
-
option_filter: Optional filter to apply to the list.
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
An iterator of options.
|
|
33
|
-
|
|
34
|
-
Notes:
|
|
35
|
-
If there is no highlight, we default at position 0.
|
|
36
|
-
|
|
37
|
-
The options are taken as a parameter, rather than just been pulled
|
|
38
|
-
out of `option_list`, so that you have a chance to `cast` the list
|
|
39
|
-
to the desired type, which in turn ensures that the return type
|
|
40
|
-
matches.
|
|
41
|
-
"""
|
|
42
|
-
option_filter = option_filter or (lambda _: True)
|
|
43
|
-
highlight = option_list.highlighted or 0
|
|
44
|
-
options = list(reversed(options)) if direction == "previous" else options
|
|
45
|
-
highlight = (len(options) - highlight - 1) if direction == "previous" else highlight
|
|
46
|
-
return (
|
|
47
|
-
option
|
|
48
|
-
for option in [*options[highlight:], *options[:highlight]][1:]
|
|
49
|
-
if option_filter(option)
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
### _after_highlight.py ends here
|
|
File without changes
|