From a158fad4854725c5ba73416cd6301bf97c40f11a Mon Sep 17 00:00:00 2001 From: Tim Bendt Date: Wed, 30 Apr 2025 13:11:00 -0400 Subject: [PATCH] refactor email viewer app --- maildir_gtd/__init__.py | 1 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 173 bytes maildir_gtd/actions/__init__.py | 1 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 181 bytes .../__pycache__/archive.cpython-311.pyc | Bin 0 -> 1840 bytes .../__pycache__/delete.cpython-311.pyc | Bin 0 -> 1912 bytes .../actions/__pycache__/next.cpython-311.pyc | Bin 0 -> 2836 bytes .../actions/__pycache__/open.cpython-311.pyc | Bin 0 -> 1376 bytes .../__pycache__/previous.cpython-311.pyc | Bin 0 -> 2048 bytes .../__pycache__/show_message.cpython-311.pyc | Bin 0 -> 1770 bytes .../actions/__pycache__/task.cpython-311.pyc | Bin 0 -> 1609 bytes maildir_gtd/actions/archive.py | 21 ++ maildir_gtd/actions/delete.py | 23 ++ maildir_gtd/actions/next.py | 36 +++ maildir_gtd/actions/open.py | 16 + maildir_gtd/actions/previous.py | 26 ++ maildir_gtd/actions/show_message.py | 22 ++ maildir_gtd/actions/task.py | 22 ++ maildir_gtd/app.py | 142 ++++++++ .../email_viewer.tcss | 0 maildir_gtd/screens/CreateTask.py | 26 ++ maildir_gtd/screens/OpenMessage.py | 25 ++ maildir_gtd/screens/__init__.py | 1 + .../__pycache__/CreateTask.cpython-311.pyc | Bin 0 -> 2181 bytes .../__pycache__/OpenMessage.cpython-311.pyc | Bin 0 -> 2349 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 181 bytes tui_email_viewer.py | 303 ------------------ 27 files changed, 362 insertions(+), 303 deletions(-) create mode 100644 maildir_gtd/__init__.py create mode 100644 maildir_gtd/__pycache__/__init__.cpython-311.pyc create mode 100644 maildir_gtd/actions/__init__.py create mode 100644 maildir_gtd/actions/__pycache__/__init__.cpython-311.pyc create mode 100644 maildir_gtd/actions/__pycache__/archive.cpython-311.pyc create mode 100644 maildir_gtd/actions/__pycache__/delete.cpython-311.pyc create mode 100644 maildir_gtd/actions/__pycache__/next.cpython-311.pyc create mode 100644 maildir_gtd/actions/__pycache__/open.cpython-311.pyc create mode 100644 maildir_gtd/actions/__pycache__/previous.cpython-311.pyc create mode 100644 maildir_gtd/actions/__pycache__/show_message.cpython-311.pyc create mode 100644 maildir_gtd/actions/__pycache__/task.cpython-311.pyc create mode 100644 maildir_gtd/actions/archive.py create mode 100644 maildir_gtd/actions/delete.py create mode 100644 maildir_gtd/actions/next.py create mode 100644 maildir_gtd/actions/open.py create mode 100644 maildir_gtd/actions/previous.py create mode 100644 maildir_gtd/actions/show_message.py create mode 100644 maildir_gtd/actions/task.py create mode 100644 maildir_gtd/app.py rename email_viewer.tcss => maildir_gtd/email_viewer.tcss (100%) create mode 100644 maildir_gtd/screens/CreateTask.py create mode 100644 maildir_gtd/screens/OpenMessage.py create mode 100644 maildir_gtd/screens/__init__.py create mode 100644 maildir_gtd/screens/__pycache__/CreateTask.cpython-311.pyc create mode 100644 maildir_gtd/screens/__pycache__/OpenMessage.cpython-311.pyc create mode 100644 maildir_gtd/screens/__pycache__/__init__.cpython-311.pyc delete mode 100644 tui_email_viewer.py diff --git a/maildir_gtd/__init__.py b/maildir_gtd/__init__.py new file mode 100644 index 0000000..11c4e8c --- /dev/null +++ b/maildir_gtd/__init__.py @@ -0,0 +1 @@ +# Initialize the maildir_gtd package diff --git a/maildir_gtd/__pycache__/__init__.cpython-311.pyc b/maildir_gtd/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4e5f86f900e6efd70544c65030fb7b62478584c GIT binary patch literal 173 zcmZ3^%ge<81jnp|GE{-|V-N=h7@>^MY(U0zh7^Wi22Do4l?+8pK>lZt|^pX_)`1s7c s%#!$cy@JYL95%W6DWy57c15f}13^|6^8<+w%#4hT9~fXn5i?K>05`}g9smFU literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__init__.py b/maildir_gtd/actions/__init__.py new file mode 100644 index 0000000..d01869e --- /dev/null +++ b/maildir_gtd/actions/__init__.py @@ -0,0 +1 @@ +# Initialize the actions subpackage diff --git a/maildir_gtd/actions/__pycache__/__init__.cpython-311.pyc b/maildir_gtd/actions/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e65fb05ca4a2300a5f6f8aef23f726d7c5fce014 GIT binary patch literal 181 zcmZ3^%ge<81ShP7GE{){V-N=h7@>^MY(U0zh7^Wi22Do4l?+8pK>lZt|^pX_)#N?99 z{JdiQ`1s7c%#!$cy@JYL95%W6DWy57c15f}gF#jo^8<+w%#4hT9~fXn5i?K>0OI{E AM*si- literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__pycache__/archive.cpython-311.pyc b/maildir_gtd/actions/__pycache__/archive.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65e3b40534060cd9b3761c964a99d981b69cd4fa GIT binary patch literal 1840 zcmb^yO=}xRboN8iT3Y!lE@@*o*(8OcLso4dt!>dnPH1fTlGT3L47lpgYUdLOg% zK4;$O`(!eX05(VaOFv5p{lS$sh@r5jgK!^VgoQG)gc(8LvRD>pMBq|cvg8>V)}lD_ zr92bCVh*KZe{$oL5JYp#U}j+w#Dq~`rd`Qb$RZ1(lrWc83PKo!vo82S5@_!(guaiK zQ5}?%fQ4l=duaCnQh6w?gT8UNCV(pz=~&KkuLpo7EaM1{G9D|0qu!4DDMoM%$9`1S z!G7b=$BM8l%pQ8jU3U%|ML&alqivr@IgYdLj=m1NH`=X5b*1yABjLo+oS;JWWQ8y6 z&i}Awr#i8g(eaw$AbWGX_b;oK^X{?;C| zQf#kjHA&a?Kq;A)Q8t#0AQo;8WXql>ff9OkZl=VbyI@pd5=h>5S=D8M%wRkyzaX{b zMTk_%7wigyxHV;>6;i9kHKUam)@x^OKsE$x%x*6prm-*RwOE!?n~rLesYH-)O7=qD zfq`_Lpg$}pALf=faS(Uh+f`~8AWk4rw-QJWqd`0zRU4C_YtAKfDQ{N@$cP};SwE$0DLMP6W)O@(2gAYYU=Kk zH;{RB%1>Pb-ol2Q_2sN5XLrS9Br9z94SsvJnLNGrTK&eO@n5B1uKQyjH->ZmaL!Y2 zHV4l&`(D|N$^G%a5Rkt)`8v8AM{2*H7^(|fiSu6Kd{gbYH?cagmj3CThI-joFF#dB zw$u@CwD=_FXFmnr!iHM%)sm-{xWUxwlsEKI-SOXi4|oe3>bS3td+PYMdU{Q2s6)Ow zxuB#dTce6jIU(8f5T@0yAt;Yf^!yHUjN|r>%-``YwrU6eK?-EDs75yXGWyW zkqF2sk@9nyOhtnpez{$vtS^|jNSJeQiD)l;4PHHR+@{Z%K*emev{1>I?!23U{C&8Zp literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__pycache__/delete.cpython-311.pyc b/maildir_gtd/actions/__pycache__/delete.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e8a06852c8c1d2fcc4998a9b8942bb760b9300c GIT binary patch literal 1912 zcmb_d&1)M+6rcT&w31eFWs;`;AYRu!RE4ZsgHvqM9Ae2N1v>|~0Xqk=(M)AIEA4i6 zHnxRbLMSBUk{k*#qyb;zwzvWR2`P2&E3*)>#SrKzHx>5~eCiv0*@~MS+73_ey_uai z@ArH2p&t^77=rfwWbeZ75<-9QPP>UMXI}^BA;Jg?CYt9~G=-8lFACh3OsOQ#%ixD` z=v#R{goRm@ioE2(Gbtem&oYA*^WepeJS$q|T$wDhAWR8!X*n;nl5nHI4?v*(9Z3BU zt)dO+9B)`yMT?!dM*!!sv&s3ZMy#T4-cbE_X8G=Q>UuaAp|{ol4R{i-;W=>}56?J)*t>_BK0x&R zn)pAW=vfsXp~vFuT<3u8n)r>#5j4FeVF@iub7)COB`d=jViHC)wm`JJODQQcjg*X{ zsg;Q17zLtNl8q_Fa-lt>RU}>4zo;ZeU}8C!x5^B{0%f6CGE8H|2qLZ6AdDNUr^Mhy z-mu|9k(}i++hu{w;FeLo15*`cx|vF=M73rB)+>Ee0D@qRS*@{7_5(q$M6#4xbV8d+ zsX^RXu$FQTTw~V>dRruNEeN?H4x(SXgs$YQGNJsK2NBaUFz^Xtj(gpvRvyv^5_QXg zWY{);B^=jefj}I-vZiSVQ|T`qLY*`# zmadaBX6X#MNleQoG!4{E%erxvS!auRC(Wyji!@hYINf}DoOF}EZm$H1)@#%pp#!iy zzvT{vSL7CYAxG|9xqZbuIr*dHe|Q0GQ>t>tmouK6sf~@_n!Gc8d-_58$+@chkaVJ|*h`|RS~$@|myrq|O?&sEh?pUXr|?dcwSu8!}j`O^aCG)j9vF=}lNosj9ZG+Fn~;?Zr>lkpTRT>Y5V2 zpSYKJU_H56Rdip`pDSa#%Gh@4nXp~jE>)F`uVlP`l2_2`5?>D-X9#Nl3%YnF(}#Z5 zJ_Wtg7t0PwJ0qWD`=#IdL*S=G8iH9fsW44~4SM(ycMVftD&hiRPV?}+cVyf3*0$^X zFQ;+F8vFbc+J|^V`#~Kj=SvoLO>%*1(B*$2&KxK}CI~f@^x9HG?|a=+LmzwHQV%Bu LUY@sU%9;KRM9IW0 literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__pycache__/next.cpython-311.pyc b/maildir_gtd/actions/__pycache__/next.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20a03a84637ae2a0a1d4de5e99f76042b89c36bb GIT binary patch literal 2836 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 literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__pycache__/previous.cpython-311.pyc b/maildir_gtd/actions/__pycache__/previous.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c074f515ef3eca964867e0eb8c86a6c6531c916 GIT binary patch literal 2048 zcmZ`)-*3}K96vjDeuvQ1TYrGSEkz@2O@<~x=`u7%sX&z)Y2B#kgsPBpTNlU9&KE+X zWI_`U6+)t#Cbimz7O|<*R33QTBO3by9As&hC8SAv8E=`o2gK99m((EzdOm;ed*APU zf82e(KK~Mng%Px8j)#vX{f->aUA1$ji2MOh=p6ioDcIe zDCvL343eDdpP@Ob=BHghQD}+id2YuO8$@`0@NORi|LZ7(XqR@m!!JU#`yA#m|G>8i z>$jLqtJSzFx9sJHuyC!#lb^7FMa@^^t9%`H@_GX+7Q59pAoVkA^MB=MnYXzzS4KB{mr=}P6$Bt`>=rhca?~q@=sy zom5l{U_}H6RQ`WCi>e@{-(m%7`R{q=$~G)60?oOteu!$J?9)A zZXz*upheaqPVb?5uRV0?N$)AE_mnd_wsy{zjyqD{pVG(^X{0`5AN{Z)Wo#*9Nf{8Y zXTQl>BbOib+Gl5Bgx-+mY-!Gt<{W8oZE|h08A8$+Tb~I~w{rS^~)I^+k`XzCKk-PER@r_u0Xd|`}dl*=aH-ck!aLjsB ze-5qb0|zjgBXHzjfAa3RVf1^tAL!#yAEU$Ji5}nM_Xa1TzCWTO@JXI9bf%Mn8)vsh zDW|2%Dqd8mN!ZVg^fAIf{1uS|;~C5^f|-JWOPX?q9EU0UXPIY#KzNRG(6F_;JE+I< PdQ*6tgDvhsS|R=i^hXeS literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__pycache__/show_message.cpython-311.pyc b/maildir_gtd/actions/__pycache__/show_message.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfd088826731155f2483b866be5a30004e2c2c9f GIT binary patch literal 1770 zcmb_cO=ufO6rR~#No#2p*QS3VX}pO8MTSV&;1sJ)53y`$QzZvCE%8OHcE;A)`=idz zMz)blD20Yxl0%_}5HN+fF>%1h9!t_oZ!05+7zh}8>P^Kx1fM!1Yu%_#dua1``{vE) zz4yI0^Pav>r;~uw&-ua1mJGlzViS?lRrW`?@-09BK^r)54nmkp6ir6a8m&_rYdz+zMfKqA)|P5t>>9d1G%gMWv#pdg6gc6^g(^H5~e zp*5*4EyklAN-TAdNChR3Y9}0ZO@=@Mb(vfB9a+KczTW)S6qNjbb^RCbjn4Z8+c;=&@Pw>f)nR9o_{=k0C>#@7wp7dAbYrMv_*cUNT z>7iDGHLxsS12vfGUmcmj)U4=+i*#i9Roht6X$9-pF)Ul}_OD)E(LXF?KUqx=^JBMU zdM?E-WopH84DMmDMAwoj1RE$Lv16uD<^7IJUO=k>WeUa1l!yl61ELlUvV^>v%k{)! z;8SaUWoGs584ih&PCb3TlT(C#0JE!!X+k`b*Dq%@*7tdU$x6v{F%iAW61Ha`%Uxhe zKPXp;XY#3IG6`HJ`;@R`ck(=hS(;d8CF^wIi6-3RK1DoB77wb(pqTYdFPpeZEzixw znQT<6L`;$xU_Y?wq0ehkh@-oJ*cYRscj(~b+*KbFKSwR6j9o-?1$-UbUKNuZhq66y z=^XXWS*D*8_lzu3TA(Orm;%+$`4z8L>QZMC`TX7hd*c%~roJwI zS-g|Gf4-rNGzE{frT*A7d^T|6n^UdyTkFR+W*$xLB)6_?UkSCz)`_9kz#Hv^GMM}g zaPhkkH{-%kHP!=XZBsL&iLgxbm0g^M?* zZWnJA*K?1~H?*;);CxH#?>&2}P3&qDVg93rcB!dd3M1b8C&O}hwG`&dhuqN2re=l_ zKYx~by$v8Q@AUX-43~*N_ zTQ2Q9)E3z1dqCdd;y|5rJQUdY12W9F;@{W*j0+A6VGEoId$|P$!id|-Ae@F9!)+ke O2WJo0f0yJ8kNpSzTBt?< literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__pycache__/task.cpython-311.pyc b/maildir_gtd/actions/__pycache__/task.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..757988b76cab9ee8ca35a6fd649ae2701f3451a5 GIT binary patch literal 1609 zcmZ`3OH3O_bY^#LV`H!h7zj~e1p&ziY^8}fBu*sqp$ZZpmGlq}b(ft192T!NGh4ud zDrzHz^wOwm1yU6WRVryk4msw~OM300cH~IbNRcY_)SD}MK%Dw!!44GhcJ{qDGw;25 zznSMPEfECpd*<9ivyRYTYElR7h}r80W)l%au!Ck4)f~K}&1e{r05KjJGdjW3C>i`) zDNH7@jNf9^67-(M7pHATXOeG6_W+jWj14r0 ziT)_Mj-R0IV>@+ughc2)zXR-UW4eZSKx4O|1)dw=aiEx-UT~L9u|UmSzTgRS$t9L! z&biD4zb%o=&zqJhAdZv_E0VR?JZH-D6QKlQp&kCcaR~5xyotg{91Hb9gD041^V-@O z0T6u$BzGIqMKDPWV!R0KK%#b4vV2&_YxqH9MuLleV65|rzO7t41tfIRE=Kte&XjEe z+kv`?AJ8-HlvHgo8rEEksWxD0YZz>*B#S+QwBMBjIxR@=I^ z5>I2>$>-@G(=P|g{UeqBk#c;b+BZ;5oUet9&enerpnnzG7^_84yt5KBw@2T^21~KQ zYP@|j_HC?WX3FvFmH73NKmM$*-hDOn>u|}u3!@w#uf)eo@$vorcd_mo(!$43H5&V7 z>g%bkiEV3ZVr$~%M;lY+Xr>a)l%knxd)Lz|^=}`b764sEgD9TZ@OU@G^wsF_=q2>} zQskzozrNOU^Md}{g#hrANki&fFlo%Xt|OZZ9$(0E|D9s$QYAH})L9Q8uKMaTlbr`5 z&z(*x>xB(m+tLuVlWi2ndfypW*) literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/archive.py b/maildir_gtd/actions/archive.py new file mode 100644 index 0000000..44023a6 --- /dev/null +++ b/maildir_gtd/actions/archive.py @@ -0,0 +1,21 @@ +from textual.widgets import Static +import subprocess + +from maildir_gtd.actions.next import action_next +def action_archive(app) -> None: + """Archive the current email message.""" + app.show_status(f"Archiving message {app.current_message_id}...") + try: + result = subprocess.run( + ["himalaya", "message", "move", "Archives", str(app.current_message_id)], + capture_output=True, + text=True + ) + if result.returncode == 0: + app.query_one("#main_content", Static).update(f"Message {app.current_message_id} archived.") + app.show_status(f"Message {app.current_message_id} archived.") + action_next(app) # Automatically show the next message + else: + app.query_one("#main_content", Static).update(f"Failed to archive message {app.current_message_id}.") + except Exception as e: + app.query_one("#main_content", Static).update(f"Error: {e}") diff --git a/maildir_gtd/actions/delete.py b/maildir_gtd/actions/delete.py new file mode 100644 index 0000000..fd8aa80 --- /dev/null +++ b/maildir_gtd/actions/delete.py @@ -0,0 +1,23 @@ +import subprocess +from textual.widgets import Static +from maildir_gtd.actions.next import action_next + + +def action_delete(app) -> None: + """Delete the current email message.""" + app.show_status(f"Deleting message {app.current_message_id}...") + app.query_one("#main_content", Static).loading = True + try: + result = subprocess.run( + ["himalaya", "message", "delete", str(app.current_message_id)], + capture_output=True, + text=True + ) + if result.returncode == 0: + app.query_one("#main_content", Static).loading = False + app.query_one("#main_content", Static).update(f"Message {app.current_message_id} deleted.") + action_next(app) # Automatically show the next message + else: + app.query_one("#main_content", Static).update(f"Failed to delete message {app.current_message_id}.") + except Exception as e: + app.query_one("#main_content", Static).update(f"Error: {e}") diff --git a/maildir_gtd/actions/next.py b/maildir_gtd/actions/next.py new file mode 100644 index 0000000..3e607aa --- /dev/null +++ b/maildir_gtd/actions/next.py @@ -0,0 +1,36 @@ +import logging +from typing import Iterable +from textual import on +from textual.app import App, ComposeResult, SystemCommand +from textual.logging import TextualHandler +from textual.screen import Screen +from textual.widgets import Header, Footer, Static, Label, Input, Button +from textual.reactive import Reactive +from textual.binding import Binding +from textual.timer import Timer +from textual.containers import ScrollableContainer, Horizontal, Vertical, Grid +import subprocess + +def action_next(app) -> None: + """Show the next email message by finding the next higher ID from the list of envelope IDs.""" + try: + result = subprocess.run( + ["himalaya", "envelope", "list", "-o", "json"], + capture_output=True, + text=True + ) + if result.returncode == 0: + import json + envelopes = json.loads(result.stdout) + ids = sorted(int(envelope['id']) for envelope in envelopes) + for envelope_id in ids: + if envelope_id > app.current_message_id: + app.current_message_id = envelope_id + app.show_message(app.current_message_id) + app.show_status(f"Showing next message: {app.current_message_id}") + return + app.show_status("No newer messages found.", severity="warning") + else: + app.show_status("Failed to fetch envelope list.", severity="error") + except Exception as e: + app.show_status(f"Error: {e}", severity="error") diff --git a/maildir_gtd/actions/open.py b/maildir_gtd/actions/open.py new file mode 100644 index 0000000..b32d315 --- /dev/null +++ b/maildir_gtd/actions/open.py @@ -0,0 +1,16 @@ +from maildir_gtd.screens.OpenMessage import OpenMessageScreen + + +def action_open(app) -> None: + """Show the input modal for opening a specific message by ID.""" + def check_id(message_id: str) -> bool: + try: + int(message_id) + app.show_status(f"Opening message {message_id}...") + app.current_message_id = message_id + app.show_message(app.current_message_id) + except ValueError: + app.show_status("Invalid message ID. Please enter an integer.", severity="error") + return True + return False + app.push_screen(OpenMessageScreen(), check_id) diff --git a/maildir_gtd/actions/previous.py b/maildir_gtd/actions/previous.py new file mode 100644 index 0000000..b7df02b --- /dev/null +++ b/maildir_gtd/actions/previous.py @@ -0,0 +1,26 @@ +from textual.widgets import Static +import subprocess + +def action_previous(app) -> None: + """Show the previous email message by finding the next lower ID from the list of envelope IDs.""" + try: + result = subprocess.run( + ["himalaya", "envelope", "list", "-o", "json"], + capture_output=True, + text=True + ) + if result.returncode == 0: + import json + envelopes = json.loads(result.stdout) + ids = sorted((int(envelope['id']) for envelope in envelopes), reverse=True) + for envelope_id in ids: + if envelope_id < app.current_message_id: + app.current_message_id = envelope_id + app.show_message(app.current_message_id) + app.show_status(f"Showing previous message: {app.current_message_id}") + return + app.show_status("No older messages found.", severity="warning") + else: + app.show_status("Failed to fetch envelope list.", severity="error") + except Exception as e: + app.show_status(f"Error: {e}", severity="error") diff --git a/maildir_gtd/actions/show_message.py b/maildir_gtd/actions/show_message.py new file mode 100644 index 0000000..1efcab2 --- /dev/null +++ b/maildir_gtd/actions/show_message.py @@ -0,0 +1,22 @@ +from textual.widgets import Static +import subprocess + +def show_message(app, message_id: int) -> None: + """Fetch and display the email message by ID.""" + app.query_one("#main_content", Static).loading = True + try: + result = subprocess.run( + ["himalaya", "message", "read", str(message_id)], + capture_output=True, + text=True + ) + if result.returncode == 0: + # Render the email content as Markdown + from rich.markdown import Markdown + markdown_content = Markdown(result.stdout, justify=True) + app.query_one("#main_content", Static).loading = False + app.query_one("#main_content", Static).update(markdown_content) + else: + app.query_one("#main_content", Static).update(f"Failed to fetch message {message_id}.") + except Exception as e: + app.query_one("#main_content", Static).update(f"Error: {e}") diff --git a/maildir_gtd/actions/task.py b/maildir_gtd/actions/task.py new file mode 100644 index 0000000..af31c5a --- /dev/null +++ b/maildir_gtd/actions/task.py @@ -0,0 +1,22 @@ +import subprocess +from maildir_gtd.screens.CreateTask import CreateTaskScreen + + +def action_create_task(app) -> None: + """Show the input modal for creating a task.""" + def check_task(task_args: str) -> bool: + try: + result = subprocess.run( + ["task", "add"] + task_args.split(" "), + capture_output=True, + text=True + ) + if result.returncode == 0: + app.show_status("Task created successfully.") + else: + app.show_status(f"Failed to create task: {result.stderr}") + except Exception as e: + app.show_status(f"Error: {e}", severity="error") + return True + return False + app.push_screen(CreateTaskScreen(), check_task) diff --git a/maildir_gtd/app.py b/maildir_gtd/app.py new file mode 100644 index 0000000..1233eaf --- /dev/null +++ b/maildir_gtd/app.py @@ -0,0 +1,142 @@ +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +import logging +from typing import Iterable +from textual import on +from textual.app import App, ComposeResult, SystemCommand +from textual.logging import TextualHandler +from textual.screen import Screen +from textual.widgets import Header, Footer, Static, Label, Input, Button +from textual.reactive import Reactive +from textual.binding import Binding +from textual.timer import Timer +from textual.containers import ScrollableContainer, Horizontal, Vertical, Grid +import subprocess +from maildir_gtd.actions.archive import action_archive +from maildir_gtd.actions.delete import action_delete +from maildir_gtd.actions.open import action_open +from maildir_gtd.actions.show_message import show_message +from maildir_gtd.actions.next import action_next +from maildir_gtd.actions.previous import action_previous +from maildir_gtd.actions.task import action_create_task + +logging.basicConfig( + level="NOTSET", + handlers=[TextualHandler()], +) + + +class EmailViewerApp(App): + """A simple email viewer app using the Himalaya CLI.""" + title = "Maildir GTD Reader" + CSS_PATH = "email_viewer.tcss" # Optional: For styling + + current_message_id: Reactive[int] = Reactive(1) + + def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]: + yield from super().get_system_commands(screen) + yield SystemCommand("Next Message", "Navigate to Next ID", self.action_next) + yield SystemCommand("Previous Message", "Navigate to Previous ID", self.action_previous) + yield SystemCommand("Delete Message", "Delete the current message", self.action_delete) + yield SystemCommand("Archive Message", "Archive the current message", self.action_archive) + yield SystemCommand("Open Message", "Open a specific message by ID", self.action_open) + yield SystemCommand("Create Task", "Create a task using the task CLI", self.action_create_task) + + BINDINGS = [ + Binding("n", "next", "Next message"), + Binding("p", "previous", "Previous message"), + Binding("d", "delete", "Delete message"), + Binding("a", "archive", "Archive message"), + Binding("o", "open", "Open message", show=False), + Binding("q", "quit", "Quit application"), + Binding("t", "create_task", "Create Task") + ] + + BINDINGS.extend([ + Binding("j", "scroll_down", "Scroll down"), + Binding("k", "scroll_up", "Scroll up"), + Binding("down", "scroll_down", "Scroll down"), + Binding("up", "scroll_up", "Scroll up"), + Binding("space", "scroll_page_down", "Scroll page down"), + Binding("b", "scroll_page_up", "Scroll page up") + ]) + + def compose(self) -> ComposeResult: + """Create child widgets for the app.""" + yield Header(show_clock=True) + yield Footer(Label("[n] Next | [p] Previous | [d] Delete | [a] Archive | [o] Open | [q] Quit | [c] Create Task")) + yield ScrollableContainer(Static(Label("Email Viewer App"), id="main_content")) + + def on_mount(self) -> None: + """Called when the app is mounted.""" + self.alert_timer: Timer | None = None # Timer to throttle alerts + # Fetch the ID of the most recent message using the Himalaya CLI + try: + result = subprocess.run( + ["himalaya", "envelope", "list", "-o", "json"], + capture_output=True, + text=True + ) + if result.returncode == 0: + import json + envelopes = json.loads(result.stdout) + if envelopes: + self.current_message_id = int(envelopes[0]['id']) # Get the first envelope's ID + else: + self.query_one("#main_content", Static).update("Failed to fetch the most recent message ID.") + except Exception as e: + self.query_one("#main_content", Static).update(f"Error: {e}") + + self.show_message(self.current_message_id) + + def show_message(self, message_id: int) -> None: + show_message(self, message_id) + + def show_status(self, message: str, severity: str = "information") -> None: + """Display a status message using the built-in notify function.""" + self.notify(message, title="Status", severity=severity, timeout=1, markup=True) + + def action_next(self) -> None: + action_next(self) + + def action_previous(self) -> None: + action_previous(self) + + def action_delete(self) -> None: + action_delete(self) + + def action_archive(self) -> None: + action_archive(self) + + def action_open(self) -> None: + action_open(self) + + def action_create_task(self) -> None: + action_create_task(self) + + + def action_scroll_down(self) -> None: + """Scroll the main content down.""" + self.query_one("#main_content", Static).scroll_down() + + def action_scroll_up(self) -> None: + """Scroll the main content up.""" + self.query_one("#main_content", Static).scroll_up() + + def action_scroll_page_down(self) -> None: + """Scroll the main content down by a page.""" + self.query_one("#main_content", Static).scroll_page_down() + + def action_scroll_page_up(self) -> None: + """Scroll the main content up by a page.""" + self.query_one("#main_content", Static).scroll_page_up() + + def action_quit(self) -> None: + """Quit the application.""" + self.exit() + +if __name__ == "__main__": + app = EmailViewerApp() + app.run() diff --git a/email_viewer.tcss b/maildir_gtd/email_viewer.tcss similarity index 100% rename from email_viewer.tcss rename to maildir_gtd/email_viewer.tcss diff --git a/maildir_gtd/screens/CreateTask.py b/maildir_gtd/screens/CreateTask.py new file mode 100644 index 0000000..6e05336 --- /dev/null +++ b/maildir_gtd/screens/CreateTask.py @@ -0,0 +1,26 @@ +from textual import on +from textual.app import ComposeResult, Screen +from textual.widgets import Input, Label +from textual.containers import Horizontal + + +class CreateTaskScreen(Screen[str]): + def compose(self) -> ComposeResult: + yield Horizontal( + Label("$>", id="task_prompt"), + Label("task add ", id="task_prompt_label"), + Input(placeholder="arguments", id="task_input") + ) + + @on(Input.Submitted) + def handle_task_args(self) -> None: + input_widget = self.query_one("#task_input", Input) + self.disabled = True + self.loading = True + task_args = input_widget.value + self.dismiss(task_args) + + @on(Input._on_key) + def handle_close(self, event) -> None: + if (event.key == "escape" or event.key == "ctrl+c"): + self.dismiss() diff --git a/maildir_gtd/screens/OpenMessage.py b/maildir_gtd/screens/OpenMessage.py new file mode 100644 index 0000000..a9234ee --- /dev/null +++ b/maildir_gtd/screens/OpenMessage.py @@ -0,0 +1,25 @@ +from textual import on +from textual.app import ComposeResult, Screen +from textual.widgets import Input, Label, Button +from textual.containers import Horizontal + +class OpenMessageScreen(Screen[int]): + def compose(self) -> ComposeResult: + yield Horizontal( + Label("📨", id="message_label"), + Input(placeholder="Enter message ID (integer only)", type="integer", id="open_message_input"), + Button("Open", variant="primary", id="open_message_button") + ) + + @on(Input.Submitted) + def handle_message_id(self) -> None: + input_widget = self.query_one("#open_message_input", Input) + self.disabled = True + self.loading = True + message_id = int(input_widget.value) + self.dismiss(message_id) + + @on(Input._on_key) + def handle_close(self, event) -> None: + if (event.key == "escape" or event.key == "ctrl+c"): + self.dismiss() diff --git a/maildir_gtd/screens/__init__.py b/maildir_gtd/screens/__init__.py new file mode 100644 index 0000000..e6ab631 --- /dev/null +++ b/maildir_gtd/screens/__init__.py @@ -0,0 +1 @@ +# Initialize the screens subpackage diff --git a/maildir_gtd/screens/__pycache__/CreateTask.cpython-311.pyc b/maildir_gtd/screens/__pycache__/CreateTask.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46429848bdea137cfb54c4a3d93363e67637daf6 GIT binary patch literal 2181 zcmZ`)Pj3@P6rbJouK#Tk0wp*K2&V}Z1`_wsav(GuKx%2z90*jZrFOO1nIxO+dd7;Gbqu;L+KDJhB=tDfp>CCyKi5|YLG_YEUkyeBf%} zR{}vQ%Z@nCx9vw*s_pd8ws(^*k=@%no|0=ta}Dk&J1 zRICtfNyX}Y@LokkC&p?rHrrLeFKAK022R{h>;m&F7NJtAW0Q_WeKvZZO$KXlz9c_# zl@dE7H*K~SorW8fgjHI0x_iQ^DQFYW_8tQH0x95oct9V<=K?KOu=-rvRl!4OQR&eq zu->QFJR5@hR)qpOp#aOpwX_iBZH)7L_<+{( z1VTJ*)8(p9$|2(!sdWW@0A#k}*$!C=JWMDZ1g{Dj6^Ew+4=Pd+)=x0vEirNa{M*lq zw-}+U7`px%5_-Lw_Ojr)LIU4k;%ki#r@z9_UJd46nZu7KcRs0`g@##Zngzk1ufP9s-MrW^FE-7K zAlO-Z>ebDehB?zTXI>{0Lzy=SNE;>6hmt+m0L}L#KmsHJH*waLRk4RG_h8DyI8 z?^;2bNYr==yUbqn2w*UC*cbLTd%x6C7t<{r-Nj5f@Pra4iMD;u%9nvdCIcNwofdJXDrJPa22q# zxM^9ptF{+us7OG1To4h*C~wjW)kWV8LxSmP5b`9XlqC*23EUq@ZX+{-ia(bm;;CQ{ zfNXTXwyNuyol|WD-MeHE8L5pMoBGyJL(et!oR~W)CPn16hK$j!xI1~Ep^r56k+!Pp zqiqxuG5JKlIPy|BIA1V%G9=%JaB6u>78 eLWO#7Z5ziFh)O?sd}qh``G?m+^X3q>booCS!2uBf literal 0 HcmV?d00001 diff --git a/maildir_gtd/screens/__pycache__/OpenMessage.cpython-311.pyc b/maildir_gtd/screens/__pycache__/OpenMessage.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..596d82bcafb23284e8051cf90650879ee4512a40 GIT binary patch literal 2349 zcmZ`)&2Jk;6rcTe>`g*a5#pvFE^Vs1G;xpsiGo@Q3ZXO&Qa_Ne5UY)6nryot?(Db_ zE0rSDLs~_&5{Efoy3t^q8B$rP;pl$GZAKqk?O>A65#mv8gElOMt{G!YU zKMcHT5RwZdthtQq7b}zyzo>BStY57$uFcsC#O3-2HO9a|;@M9E>eK_D*>0t?bNosD z?0yUE1|p~|VN{l}M3gHsmTy5MG9nt$uNtY@wgl&cHyK#R>MeB}nER;+l`|cifmGCI zqxIQju>x@#eB`q#@z0Ymv=>Pd7S}0ZHR@NoPGMWI^XU`OlIUrdCS>U_NMQ(IhJ{mwataJ}Y6c?(TtGafD zECntml&fsHO6V{+%X}Y`V<%^cXKBSdh7K!={eyv)HDnQjtn3qNM`eGy??A zsfNV85{i@-o}MdR4haoQ%<&e8k6Gyqxj|gmotA*g)752eR1)mg^Uo=gN>6uG;xM7W=|AvWMLR>|JLbr}qnlP_PBqM_ra1+C zbl~(KPIPH0GM$F$G)-s69KH9>*1OT0A4leF!<=oJvyTmRIQs+vX(M&ug=81pp!ptb ziQ)c!Vhz}yP5y84*wVv=<)20|;+)h-r$ugOi=VJmGo zJ?>ZJ-j$-U0Pd9e`kuo*HID&v32xQE^%|keR^XGk$UK9c&|Yu}=7t;C*zp&+0+{34 z4cn~|Zh+cz!my~&aq!H|cq8j;2QL!Fvt2ymP{dRy6sYK5d}PV?u}hxe71s~<7rAfI z5ZH!uKvvOT69<1f^~0&?_~}1iZA{EICuY~?TKfuXXV%ZRCJw@9ZYRHQ^ULP`QX@ap z%+IWS+A?3cJ9lSpb8o{u(ln1m=|d-A!%DXRz=(NV)~{d!WtBt^PX?B*#Vf;Hk5o>i zbPNjdTysgSpy)7t*7uS!bV|W>5?1Ugsp}O+-8U-GwUujRnZ6DtML9{U2G_`Ep>Un1 z(ClQV;zE0`A9!i@V*MBxhHn9Z#F-=OXE(l#IktSvb+G-ppX_fE$e#Cb`uR1H-qjK zL?kkS(!j;qg6A+sFr5S;Hvnapi1avczoFPo3=f(UHi8IHhqr;Oc7JVI)3TfU+Xz1Y zCqqcjtX^2t)`uI~cvBk}YbV8`ggjP|p6`mgiw7IpSW_Eo%d(blqm+onul0+QQ;M_A z7YDh)$Ttk4j-%wc4mWLAt4RjLh2_JkIJp&J(mmroDq1KXP4yM=x9h;o3zJq`0Bw?s z88H($jq~#~^L~={Q}k_+3BZLLK-!8VNi8%M^*${$6}{+bp_%CAPYV^J-lwhZksv|+ TWK-V^f6P4=nkO$%#|Qrp26i;Q literal 0 HcmV?d00001 diff --git a/maildir_gtd/screens/__pycache__/__init__.cpython-311.pyc b/maildir_gtd/screens/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..deaa918ab2a0566714dee36fa895d5428e434abe GIT binary patch literal 181 zcmZ3^%ge<81hcG!GE{){V-N=h7@>^MY(U0zh7^Wi22Do4l?+8pK>lZt|^pX_);^d;# z)VyN-`1s7c%#!$cy@JYL95%W6DWy57c15f}gF#jo^8<+w%#4hT9~fXn5i?K>0K}aw A8vp ComposeResult: - yield Horizontal( - Label("📨", id="message_label"), - Input(placeholder="Enter message ID (integer only)", type="integer", id="open_message_input"), - Button("Open", variant="primary", id="open_message_button") - ) - - @on(Input.Submitted) - def handle_message_id(self) -> None: - logging.info("Open message") - input_widget = self.query_one("#open_message_input", Input) - self.disabled = True - self.loading = True - message_id = int(input_widget.value) - self.dismiss(message_id) - - @on(Input._on_key) - def handle_close(self, event) -> None: - if (event.key == "escape" or event.key == "ctrl+c"): - self.dismiss() - - -class CreateTaskScreen(Screen[str]): - def compose(self) -> ComposeResult: - yield Vertical( - Label("$>", id="task_prompt"), - Label("task ", id="task_prompt_label"), - Input(placeholder="arguments", id="task_input") - ) - - @on(Input.Submitted) - def handle_task_args(self) -> None: - input_widget = self.query_one("#task_input", Input) - self.disabled = True - self.loading = True - task_args = input_widget.value - self.dismiss(task_args) - - @on(Input._on_key) - def handle_close(self, event) -> None: - if (event.key == "escape" or event.key == "ctrl+c"): - self.dismiss() - - -class EmailViewerApp(App): - """A Textual app for viewing and managing emails.""" - title = "Mail Reader" - CSS_PATH = "email_viewer.tcss" # Optional: For styling - - current_message_id: Reactive[int] = Reactive(1) - - def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]: - yield from super().get_system_commands(screen) - yield SystemCommand("Next Message", "Navigate to Next ID", self.action_next) - yield SystemCommand("Previous Message", "Navigate to Previous ID", self.action_previous) - yield SystemCommand("Delete Message", "Delete the current message", self.action_delete) - yield SystemCommand("Archive Message", "Archive the current message", self.action_archive) - yield SystemCommand("Open Message", "Open a specific message by ID", self.action_open) - yield SystemCommand("Create Task", "Create a task using the task CLI", self.action_create_task) - - BINDINGS = [ - Binding("n", "next", "Next message"), - Binding("p", "previous", "Previous message"), - Binding("d", "delete", "Delete message"), - Binding("a", "archive", "Archive message"), - Binding("o", "open", "Open message", show=False), - Binding("q", "quit", "Quit application"), - Binding("c", "create_task", "Create Task") - ] - - BINDINGS.extend([ - Binding("j", "scroll_down", "Scroll down"), - Binding("k", "scroll_up", "Scroll up"), - Binding("down", "scroll_down", "Scroll down"), - Binding("up", "scroll_up", "Scroll up"), - Binding("space", "scroll_page_down", "Scroll page down"), - Binding("b", "scroll_page_up", "Scroll page up") - ]) - - def compose(self) -> ComposeResult: - """Create child widgets for the app.""" - yield Header(show_clock=True) - yield Footer(Label("[n] Next | [p] Previous | [d] Delete | [a] Archive | [o] Open | [q] Quit | [c] Create Task")) - yield ScrollableContainer(Static(Label("Email Viewer App"), id="main_content")) - - def on_mount(self) -> None: - """Called when the app is mounted.""" - self.alert_timer: Timer | None = None # Timer to throttle alerts - # Fetch the ID of the most recent message using the Himalaya CLI - try: - result = subprocess.run( - ["himalaya", "envelope", "list", "-o", "json"], - capture_output=True, - text=True - ) - if result.returncode == 0: - import json - envelopes = json.loads(result.stdout) - if envelopes: - self.current_message_id = int(envelopes[0]['id']) # Get the first envelope's ID - else: - self.query_one("#main_content", Static).update("Failed to fetch the most recent message ID.") - except Exception as e: - self.query_one("#main_content", Static).update(f"Error: {e}") - - self.show_message(self.current_message_id) - - def show_message(self, message_id: int) -> None: - self.query_one("#main_content", Static).loading = True - """Fetch and display the email message by ID.""" - try: - result = subprocess.run( - ["himalaya", "message", "read", str(message_id)], - capture_output=True, - text=True - ) - if result.returncode == 0: - # Render the email content as Markdown - from rich.markdown import Markdown - markdown_content = Markdown(result.stdout, justify=True ) - self.query_one("#main_content", Static).loading = False - self.query_one("#main_content", Static).update(markdown_content) - else: - self.query_one("#main_content", Static).update(f"Failed to fetch message {message_id}.") - except Exception as e: - self.query_one("#main_content", Static).update(f"Error: {e}") - - def show_status(self, message: str, severity: str = "information") -> None: - """Display a status message using the built-in notify function.""" - self.notify(message, title="Status", severity=severity, timeout=1, markup=True) - - def action_next(self) -> None: - """Show the next email message, iterating until a valid one is found or giving up after 100 attempts.""" - try: - result = subprocess.run( - ["nvim", "--server", " /tmp/nvim-server", " --remote-send", "':Himalaya'"], - capture_output=False, - text=True - ) - except Exception as e: - logging.warning(f"Error running nvim himalaya refresh command. Maybe the nvim server isn't started? {e}") - return - self.query_one("#main_content", Static).loading = True - attempts = 0 - while attempts < 100: - self.current_message_id += 1 - try: - result = subprocess.run( - ["himalaya", "message", "read", str(self.current_message_id)], - capture_output=True, - text=True - ) - if result.returncode == 0: - self.query_one("#main_content", Static).loading = False - self.show_message(self.current_message_id) - self.show_status(f"Showing next message: {self.current_message_id}") - return - else: - attempts += 1 - except Exception as e: - self.query_one("#main_content", Static).update(f"Error: {e}") - return - self.query_one("#main_content", Static).update("No more messages found after 100 attempts.") - - def action_previous(self) -> None: - """Show the previous email message, iterating until a valid one is found or giving up after 100 attempts.""" - self.show_status("Loading previous message...") - attempts = 0 - while attempts < 100 and self.current_message_id > 1: - self.current_message_id -= 1 - try: - result = subprocess.run( - ["himalaya", "message", "read", str(self.current_message_id)], - capture_output=True, - text=True - ) - if result.returncode == 0: - self.show_message(self.current_message_id) - self.show_status(f"Showing previous message: {self.current_message_id}") - return - else: - attempts += 1 - except Exception as e: - self.query_one("#main_content", Static).update(f"Error: {e}") - return - self.query_one("#main_content", Static).update("No more messages found after 100 attempts.") - - def action_delete(self) -> None: - """Delete the current email message.""" - self.show_status(f"Deleting message {self.current_message_id}...") - self.query_one("#main_content", Static).loading = True - try: - result = subprocess.run( - ["himalaya", "message", "delete", str(self.current_message_id)], - capture_output=True, - text=True - ) - if result.returncode == 0: - self.query_one("#main_content", Static).loading = False - self.query_one("#main_content", Static).update(f"Message {self.current_message_id} deleted.") - self.show_status(f"Message {self.current_message_id} deleted.") - self.action_next() # Automatically show the next message - else: - self.query_one("#main_content", Static).update(f"Failed to delete message {self.current_message_id}.") - except Exception as e: - self.query_one("#main_content", Static).update(f"Error: {e}") - - def action_archive(self) -> None: - """Archive the current email message.""" - self.show_status(f"Archiving message {self.current_message_id}...") - try: - result = subprocess.run( - ["himalaya", "message", "move", "Archives", str(self.current_message_id)], - capture_output=True, - text=True - ) - if result.returncode == 0: - self.query_one("#main_content", Static).update(f"Message {self.current_message_id} archived.") - self.show_status(f"Message {self.current_message_id} archived.") - self.action_next() # Automatically show the next message - else: - self.query_one("#main_content", Static).update(f"Failed to archive message {self.current_message_id}.") - except Exception as e: - self.query_one("#main_content", Static).update(f"Error: {e}") - - def action_open(self) -> None: - """Show the input modal for opening a specific message by ID.""" - def check_id(message_id: str) -> bool: - try: - int(message_id) - self.app.show_status(f"Opening message {message_id}...") - self.app.current_message_id = message_id - self.app.show_message(self.app.current_message_id) - except ValueError: - self.app.show_status("Invalid message ID. Please enter an integer.", severity="error") - return True - except ValueError: - return False - self.push_screen(OpenMessageScreen(), check_id) - - - def action_create_task(self) -> None: - """Show the input modal for creating a task.""" - def check_task(task_args: str) -> bool: - try: - result = subprocess.run( - ["task"] + task_args.split(), - capture_output=True, - text=True - ) - if result.returncode == 0: - self.show_status("Task created successfully.") - else: - self.show_status(f"Failed to create task: {result.stderr}") - except Exception as e: - self.show_status(f"Error: {e}", severity="error") - return True - return False - self.push_screen(CreateTaskScreen(), check_task) - - - def action_scroll_down(self) -> None: - """Scroll the main content down.""" - self.query_one("#main_content", Static).scroll_down() - - def action_scroll_up(self) -> None: - """Scroll the main content up.""" - self.query_one("#main_content", Static).scroll_up() - - def action_scroll_page_down(self) -> None: - """Scroll the main content down by a page.""" - self.query_one("#main_content", Static).scroll_page_down() - - def action_scroll_page_up(self) -> None: - """Scroll the main content up by a page.""" - self.query_one("#main_content", Static).scroll_page_up() - - def action_quit(self) -> None: - """Quit the application.""" - self.exit() - -if __name__ == "__main__": - app = EmailViewerApp() - app.run()