1212from textual .widgets import Header , Footer , ListItem , ListView , Rule , Select , Tree , Markdown , Static , Button , \
1313 Label , Input , Pretty , Switch
1414from textual .reactive import reactive
15+ from rich .emoji import Emoji
1516
1617
1718CONFIG_DIR = Path .joinpath (Path .home (), ".config/lugus" )
@@ -77,6 +78,20 @@ def update(self, table, id_record, values):
7778 )
7879 self .connection .commit ()
7980
81+ def update_where (self , table , where , values ):
82+ cursor = self .connection .cursor ()
83+ cursor .execute (
84+ "UPDATE %s SET %s WHERE %s" % (
85+ table ,
86+ ", " .join ([
87+ f"{ v [0 ]} = { self ._convert_value (v [1 ])} "
88+ for v in values
89+ ]),
90+ where ,
91+ )
92+ )
93+ self .connection .commit ()
94+
8095 def read (self , table , id_record ):
8196 if isinstance (id_record , str ):
8297 id_record = int (id_record .lower ().replace ("id-" , "" ))
@@ -245,15 +260,20 @@ def compose(self) -> ComposeResult:
245260 },
246261 )
247262 yield tree
248- yield Button (
249- "Sync" ,
250- variant = "primary" ,
251- id = "sync" ,
252- )
253- yield Button (
254- "Add New Feed" ,
255- variant = "primary" ,
256- id = "add_new" ,
263+ yield Horizontal (
264+ Button (
265+ Emoji .replace (":counterclockwise_arrows_button:" ),
266+ variant = "primary" ,
267+ id = "sync" ,
268+ tooltip = "Get new articles from feeds" ,
269+ ),
270+ Button (
271+ Emoji .replace (":heavy_plus_sign:" ),
272+ variant = "primary" ,
273+ id = "add_new" ,
274+ tooltip = "Add a new feed" ,
275+ ),
276+ id = "feed_buttons" ,
257277 )
258278
259279
@@ -263,18 +283,41 @@ class Articles(Vertical):
263283 recomposes = reactive (0 , recompose = True )
264284 feed = reactive (False , recompose = True )
265285 filter_articles_type = reactive ("" , recompose = True )
286+ filter_articles = reactive ("" , recompose = True )
266287
267288 def compose (self ) -> ComposeResult :
268289 articles = []
269290 if self .feed and self .feed .data :
270291 data = self .feed .data
292+ # Filter by Read/Unread
271293 if self .filter_articles_type == "unread" :
272294 read = " AND read = 0"
273295 elif self .filter_articles_type == "read" :
274296 read = " AND read = 1"
275297 else :
276298 read = ""
277- articles = self .orm .search ("article" , ["id" , "date" , "title" ], where = f"feed_id = { data ["id" ]} { read } " )
299+ # Filter by Title/Content
300+ if self .filter_articles :
301+ filters = self .filter_articles .split ("," )
302+ filter_text = ""
303+ for filter in filters :
304+ filter = filter .strip ().replace ("\" " , "" )
305+ if filter :
306+ if filter .lower ().startswith ("title:" ):
307+ filter = filter .replace ("title:" , "" )
308+ filter_text = f"{ filter_text } AND title LIKE \" %{ filter } %\" "
309+ elif filter .lower ().startswith ("content:" ):
310+ filter = filter .replace ("content:" , "" )
311+ filter_text = f"{ filter_text } AND content LIKE \" %{ filter } %\" "
312+ else :
313+ filter_text = f"{ filter_text } AND (title LIKE \" %{ filter } %\" OR content LIKE \" %{ filter } %\" )"
314+ else :
315+ filter_text = ""
316+ articles = self .orm .search (
317+ "article" ,
318+ ["id" , "date" , "title" ],
319+ where = f"feed_id = { data ["id" ]} { read } { filter_text } " ,
320+ )
278321 yield ListView (
279322 * [
280323 ListItem (
@@ -292,15 +335,24 @@ def compose(self) -> ComposeResult:
292335
293336class ArticlesArea (Vertical ):
294337
338+ orm = ORM ()
339+
295340 def compose (self ) -> ComposeResult :
296- yield Select (
297- (
298- ("Unread" , "unread" ),
299- ("Read" , "read" ),
300- ("All" , "all" ),
341+ yield Horizontal (
342+ Select (
343+ (
344+ ("Unread" , "unread" ),
345+ ("Read" , "read" ),
346+ ("All" , "all" ),
347+ ),
348+ value = "unread" ,
349+ id = "filter_articles_type" ,
350+ ),
351+ Input (
352+ placeholder = "Search " + Emoji .replace (":magnifying_glass_tilted_left:" ),
353+ id = "search_articles" ,
301354 ),
302- value = "unread" ,
303- id = "filter_articles_type" ,
355+ classes = "w100 hauto mb1" ,
304356 )
305357 yield Articles (
306358 id = "articles"
@@ -311,6 +363,11 @@ def select_changed(self, event: Select.Changed) -> None:
311363 if event .select .id == "filter_articles_type" :
312364 self .query_one ("#articles" ).filter_articles_type = event .value
313365
366+ @on (Input .Changed )
367+ def filter_articles (self , event : Input .Changed ) -> None :
368+ if event .input .id == "search_articles" :
369+ self .query_one ("#articles" ).filter_articles = event .value
370+
314371
315372class Reader (Vertical ):
316373
@@ -351,6 +408,7 @@ class LugusApp(App):
351408 ("c" , "open_configuration" , "Configs" ),
352409 ("question_mark" , "article_data" , "Article Raw Data" ),
353410 ("r" , "article_read" , "Set Article as Read" ),
411+ ("a" , "all_articles_read" , "Set all Feed Articles as Read" ),
354412 ("o" , "read_online" , "Read Online" ),
355413 ]
356414
@@ -423,7 +481,7 @@ def _synchronize_feeds(self, update_interface_element=None):
423481 )
424482 if update_interface_element :
425483 update_interface_element .disabled = False
426- update_interface_element .label = "Sync"
484+ update_interface_element .label = Emoji . replace ( ":counterclockwise_arrows_button:" )
427485 self .notify ("Feeds synchronized!" )
428486 return
429487
@@ -486,6 +544,19 @@ def action_article_read(self) -> None:
486544 def action_open_configuration (self ) -> None :
487545 self .status = "configuration"
488546
547+ def action_all_articles_read (self ) -> None :
548+ feed = self .query_one ("#articles" ).feed
549+ if not feed :
550+ self .notify ("Select a feed" , severity = "warning" )
551+ else :
552+ self .orm .update_where (
553+ "article" ,
554+ f"feed_id = { feed .data ["id" ]} AND read = 0" ,
555+ [("read" , 1 )],
556+ )
557+ self .query_one ("#sidebar" ).recomposes += 1
558+ self .query_one ("#articles" ).recomposes += 1
559+
489560 def action_read_online (self ) -> None :
490561 reader = self .query_one ("#reader" )
491562 if not reader .id_article :
0 commit comments