From 1f75a03553a72a883dc892d330f70db7926e94aa Mon Sep 17 00:00:00 2001 From: Tim Bendt Date: Thu, 1 May 2025 11:59:28 -0400 Subject: [PATCH] working good state --- .../__pycache__/archive.cpython-311.pyc | Bin 1751 -> 1400 bytes .../__pycache__/delete.cpython-311.pyc | Bin 1912 -> 1586 bytes .../__pycache__/newest.cpython-311.pyc | Bin 0 -> 1690 bytes .../actions/__pycache__/next.cpython-311.pyc | Bin 2899 -> 2815 bytes .../__pycache__/oldest.cpython-311.pyc | Bin 0 -> 1669 bytes .../__pycache__/previous.cpython-311.pyc | Bin 1975 -> 1996 bytes .../__pycache__/show_message.cpython-311.pyc | Bin 1821 -> 866 bytes maildir_gtd/actions/archive.py | 5 +- maildir_gtd/actions/delete.py | 7 +- maildir_gtd/actions/newest.py | 23 ++++ maildir_gtd/actions/next.py | 6 +- maildir_gtd/actions/oldest.py | 23 ++++ maildir_gtd/actions/previous.py | 5 +- maildir_gtd/actions/show_message.py | 27 ++-- maildir_gtd/app.py | 118 +++++++++++------- maildir_gtd/email_viewer.tcss | 12 +- maildir_gtd/widgets/EnvelopeHeader.py | 24 ++++ maildir_gtd/widgets/__init__.py | 1 + .../EnvelopeHeader.cpython-311.pyc | Bin 0 -> 1398 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 185 bytes 20 files changed, 173 insertions(+), 78 deletions(-) create mode 100644 maildir_gtd/actions/__pycache__/newest.cpython-311.pyc create mode 100644 maildir_gtd/actions/__pycache__/oldest.cpython-311.pyc create mode 100644 maildir_gtd/actions/newest.py create mode 100644 maildir_gtd/actions/oldest.py create mode 100644 maildir_gtd/widgets/EnvelopeHeader.py create mode 100644 maildir_gtd/widgets/__init__.py create mode 100644 maildir_gtd/widgets/__pycache__/EnvelopeHeader.cpython-311.pyc create mode 100644 maildir_gtd/widgets/__pycache__/__init__.cpython-311.pyc diff --git a/maildir_gtd/actions/__pycache__/archive.cpython-311.pyc b/maildir_gtd/actions/__pycache__/archive.cpython-311.pyc index 1ad86694f9ebe55fa8d73d6d0c9db4a866ac6da3..56252b1bb479f2a0df70a5101d28264db64826a4 100644 GIT binary patch delta 506 zcmcc4`-6*bIWI340}z~>CY*7OX(C@IXBi^{!*qrkh6NKBicH)xgITbIck*6FSvKBa zsGtKw3Rewd7CT5A2+U@f%ap>sjER9^H4sApBSQ*My)r|I08E5|L4~16rIra|uq8tZ zPYn}xosmGjwM=zPAQK?0a>jCo3g$?Laz;(Q$&Z=pbtPSkit>vT5{r^EGRrda(iL)3 zi;EM}Q>_$kv8IBgs@TCQtQ7n-IVW#qcIGW&0XpOsTX9JWP=2x?OEb3=P=FPPi$5|< zp3kDr%*|>r`4o%mWCvD>djBr}3o`CI*!J)ODIhu_evv2e3Qyn#82WLSTjT=+BdY;| zxWU2Q&)>yAgLi@A3|=5vevw1}3Wxp$Z0H9Nd|(D@2hzoIKu0Wa*31#FkPvq-lbz@>+m_9j=QEKA0X{;VK3=7yME)VFGt6Z|cWop?ia;$>9TUiW2&S$+Q;RC&^Yc=Rn1Oz}#a3F7 zl30>j!~*0@=4Wo^mIg9dfwxGB8STr z4wnmP$OO#)0R$hIK?VXD#d1L3EpW7VmSQ-hXbvO~OL4gTZlX-=wLkphqlN_fSQlVw@0)cF}f4#h_n$pcjb03_SW A$p8QV diff --git a/maildir_gtd/actions/__pycache__/delete.cpython-311.pyc b/maildir_gtd/actions/__pycache__/delete.cpython-311.pyc index 5e8a06852c8c1d2fcc4998a9b8942bb760b9300c..bc41998395fcbbbc6a40bea9e7cd30172639924e 100644 GIT binary patch delta 530 zcmeytw~2>$IWI340}x!9CY<3gk++NU6(a+~bcPy+1rryFOx$vfSv!Sm@$mT+zaSm3q4oOnKWd*V|nTl9}%KS9hii9TbWQ%1Co-EF8r6|A%bN~*b2xKk*8flQo delta 668 zcmdnQ^Mj9fIWI340}#w{7RtCik++NW2onRt^vMqxr6z8>!k96+n^9)sSCz@VK>k-L zrUjf67m7~)#V9PajFEw1H4sApBSVSkFYZ$WN%4Rd9@XcjL*O~&< zq|8tv1~ZI-L4~2nrj`lCAXEMtrr8X0nb0*yGNcI9GSxAG41uu98Os?em?Ig=88rnb zw==bhaQLPc7bm8tDpYYQq@?DgmZYZWP3C5nGcwCo~>-W3kL>l~(+I7~O>9$?&%yCL@?hszZXmkY?q6~_4i1Rt0| z1_Bwy^1!${$lxUBBFS)A!5l~)k>qlfXFj5A=qkm0REibI)?_MT1*-DXWGfP!T*(^C U7&-YmtChMSBgjek$Rd#807~!2wg3PC diff --git a/maildir_gtd/actions/__pycache__/newest.cpython-311.pyc b/maildir_gtd/actions/__pycache__/newest.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a186f51ae485c8ef6766dfdfc12e5d88e2a2079d GIT binary patch literal 1690 zcmZ`4-)mb{_}pLld9${0y0)wJ)>&MTCXN+ZwH8^s(K%?rYRe`PZgWnXTW{_S=bS8U zNo2?#!Vvc&w5Y=(qbl9Q9(|PQACQcI;X*<1Mc*RzVf4xGB)w^h+jmdC^PTVa{l4#> z{1A&p5WsI=#mGzKFCMJ!An4FA&jtOqnik;Sh=I#l(4jb658KFY0e8!!VA>N zNJlne)8Gd_(ZMcow@?`IF6ml_UIoA3OIXJ8LuD8A_k>Lg%2G*M^Rs}spSEba3>j#) zQkF~dj_T`n?_ecjw?M?+>%=N6JmyOf!ht)eq|BgQVC{9-kVkMABJFh|m(`Mr)q##i z38jPnp@eoqKHa^S&{{|5WDXL-+5!T*aJRls;}8x%jGT<$wm66QX#K==}%W@0MAUV^rjT~Xj zTqVZxhOuHf*m71|S`Jy~hV2#!HLlGVE7Z-k*tW&E;jS3OxkYR@Pe90$iJ%wCS~=4; zH%u?&i#$~*Z^~n?r>-&A0g8F*1i*(0**ldn^DtT@?G|{xz&({iKq`7O_rGbo2;77B z09{8COk4?lGEkPfr>-Pnblk_2eOW|Xl7zk(Y|XXo$rheVNS2;t#UlZ3C&rKX^(N&wOLBk4_h=1qLi88mg}&TLyCm) zWPal?Jh@7sig`MuzssppI9-Y5`ZacffzjbPZ zWA(wYHn$refCYlL2|`ODZL&Fh6;dMD;I6SkcqV(?C((`xG6VL>#BFwrBU1+-V> z5j#;(Itn~-Ww29lCEP|$wNIPa?>qa|a6_N1wpQcpD~-Mvn?W@m`3C{|SD;Fu89{oy z9v#||o#sKSp5OJMC;HI#LjC27HGRCUk5~2ah90k6Z3a-R z_ul;7`RdT*FU87yO`osp^HqO6-H)DbB1xM#MvYMPUeDd0?by!9c5FNLC|K#Kg)Y@Y zm#WWNPXU?=IDnlELtQ?{Lk3(k!9*Io%Dv#e7oQ^8LMm3-lG8K389PHvRp;gVF t5RD63RQoVAnR9W$CR6lvIEq`Br2s()N%{lzRsGdeUyvZ_=b)Cu{{X(@uUh~B literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__pycache__/next.cpython-311.pyc b/maildir_gtd/actions/__pycache__/next.cpython-311.pyc index e633e9626160b46a3b21ced8aecee74abb82c882..5d71eed839a6e070e5aa41d341a94b82b7133628 100644 GIT binary patch delta 527 zcmcaC_Ft5DIWI340}$MsA)GONBky%aMv=)+8U1)?rm&>2<}lYX)i5n!ne50UJUN)j zU#f=1g(23cmbHepgdd~}0%{ntI41M6it%K#fCP#pY8V!9g7nBU)UYmNWMEhg#1O#9 zz{rro8qCmR3{=AfHrJV1YVu-cQHf*>Xh!zQQ&?2E zs+e?(Z?RYc!R8$-m5h_E*|M36lqPRxGnVE}OfJdH&x_AXEl({jxy9*Pk(^op647Lz z{E1CDfIBs>EHx*;AT>TS~l%67YgU90n7){P)kLSN3uk?X|nO7Q2OyHjUj6FnH_7acmMIQMpJn|P{XmSKc zp(3xd1j8Xg9S~_R4kQmtaJjHEA6AlbVPZbQ#OlJn`4xu`IJNN_WMzkmaWkaw)v(WIn9Br|Imaw6$zI0}vLuDI7RV}REN7@-j$|lj z)a0N1k2#u=b#fGo%H|H1N=B}i|AC-L0cho9Mz(C;Tb!wRrMam^i6yB;ij(u$jCnX+ zE0R+SN;32FG&v^kVN;G^&CE+lt+>UVnpc*ZlV6Y;pP6!tF}0WjXoLb36e$Bax409N zLE7T;QY%V|Y=9zsKwO*!49*6I4=f_AmUjiEr^wylvAO_8ll9r-rS7WfZ(v@Sw;{K2M diff --git a/maildir_gtd/actions/__pycache__/oldest.cpython-311.pyc b/maildir_gtd/actions/__pycache__/oldest.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..319cbc03acb148dd0550eeb33186a5c9b7d5c5c1 GIT binary patch literal 1669 zcmZ`3O=}xRboNW1t2kDZ#&+Cz-6SPCNNR{FadAzFonSX$AKV6#Qq;9(Y^}T66*HsA zHc}f(4kDE1Qm|>RoI{R1mh=xuK~S+^DD=|aRNO=Gsc$5&EVs$q(R**+_q=(U zA7ily0{G(VcrK|R^oQ8^ARjP$uLHAzFv60JZXv8<<*s^5!qNgtXnzT#IWIs7FH$Ef z9ry^M!4G1hy@w#(Kw-ptv@2bH8R9;au#Dv=$~M^V2%k1oq_VW)R{?oHYxDF|s6exo zid>erRNuCjf|tnMhHc2R)6G>;c+A%zgada`S(!%LAlm7Q|Kp*SRjdwnwPlnE`kOM^ z3i*65Eu)pL&CzHQ!rB4?zi_X;&*LBtJqd5aDR;W3?s%L<{Dg0X&^biUSF~3~iIlbP z&;$8pv2H^CiuSoCs_1Yfsf1RQo2VouqU#?ohM z;+nB!IoNWR+g1))<%aE+2sLJ>jV0>l+hW^d+;Eo+;@l>-TOgohsYK8V<*dAEn`@>Q z@>QNHjMwF)>!~Zub%0}@Iu7t*LiUbj%>s-S$+$&cC~{BbkdTVr%>8fDE`fCKeL&Zd z1k+YVpA1%{-ia$q7#-f@(R;apHY5psHPoJI*^@0imykTgayV2Wr{1Ok2+Txc!`S~I z{ShOSrMZ<~Bo5~3Y0^0-O)qntue$F!5&b#0aw{lgZ`fGTZfx#c=6?b?_yo+_;E zhbNW^bg@7u*Nu)gbD*5C-K=S|$yC>(Pt=eC^e23GP;GDq*3%0Q&Ti(m`1ekIDA^cF zcBH*{4?NKNnQKr7!3KAYCBn10!^;sDkn%J_sY~I|AqJ*4|tmGnwWV3#xYzLP?vIoy?C}rDRFq_V1hCh_H+hQ<_o-N zd&juRi_8mOUCok$Xi_5JDQ2M{9u`)#c_&HO9~-E`f@~6D;$n<8(Bqe6|svtxh%1UwGXA z=tgy_p}*PG`+n1hpX-u~{ zpRf7r#cuRO3rX7eA!>%A_xtYkZN|1nH)ETzr@?ApJ#@Jdx?FqNdI8WV!Z literal 0 HcmV?d00001 diff --git a/maildir_gtd/actions/__pycache__/previous.cpython-311.pyc b/maildir_gtd/actions/__pycache__/previous.cpython-311.pyc index 9bd94cd15b97f057f66cc93c75a3cc0a517c634e..7b174acb6187efba931ed5a8f7aa72e8e71bb762 100644 GIT binary patch delta 376 zcmdnae}pZfTcw{f~$Y0@+zW_s%C$JS8sxcgr*Kt;3ILs>!BGtHDM41m8NVy0w z9}!>$vNf5Cn1NRK0cmj{U8FJDi(T15AISW`%*e?2fI;vA41Hh`W3>FhfP*L!0_p(( D6JuRz delta 362 zcmX@Zzn!0VIWI340}#yY7Rr#G$SY~C1msL-NMT4}%wfo7jACR2v6*t1qL@-xQ&?J9 zqL@>df*CZ~UV?OKGTvefE=erOOrGd2&&V+`-H&nd#%)TBlb9?S#U@)Z9bjag{F_N- zvLZZ$T4zGW@Gi2=5(z{PAw?O%+J$gzr~zbP;iT_D7CmWr=*B; zay_e?k~+`|79cJTW@KP!VEDiy%xZa8Pp8XyJl;Sw`7B$pyAZ=6 zc?oAAX(tKfwtF@eFO6bBqR@DWLfGs~b~kKx!^}i&=^=-L z-aPi|DYmr!FA3xj1_Z&Aw@5toF!~xqK<9!HNzepI z65KRM>e#4(wU3JF-zr@xVwtbfkD_Y_FYJ&6l*we}RdxSawW!HNK;P3qroDNF%S@Yc zZo9PZ6PaXGbXn+JHGGmhA{X3c1CBRNQ#Gb0BYB53t+NCad~A0_dTi z_cwaGBjfH5BYXUEP}umse%d;-F05hU@z5xZjM5-pQ#g-`DW-~LhIbns7CA6Cxg_R; z-d0GH_}?TB;(`jp)PR}F`M0+{F+<1xzj zX5SbU2JsrB&B4tzNg}-3OO4RVII}#?-}@X6^J}C0+93B}lF+gC2Lb$5FquI{$2(uw PmA!NNaJKyyW<>u1r#{{V literal 1821 zcmb_cO=ufO6rR~#No#54)S_+L=Es}b5EO_+4R)|`dx&L26PyIx9O8>u?~J8&_D7wa zjcg+aQwlY?B!@x*A)rg#7(3u&kAB*jA$ucusg!RLx2E+HgMo9gaS*p0sYCO1Nkt7AKen#5JWZ ztJqZj<5_{-yWIN_)L;!1g}<{9x9Tvm4(~{!QSRKm&hKs>L@kPVoz(+qPlN$jh(#F5 zNI@~0j%>hr*E!GT0ar;u5W%hZI*+w^5N}1QNehYS7E0XiaFTi27x{Q3Y2kpAM^KOE zwj!gR|5ww0xn1vcz}p=6QB6KtGyl%%8Sw7nqht*}1}OF9Se^kuKYW|fARyx_$~)xN zYRY5qMEZM$w|I>!%3VcNx{s=`3Kr#CpbE3S%NM3FHOso;A{|+N#Wt37TE;qd49nKL zovhC->0cIdUoEExxZ5q7o=dSynOe3SgWoV%qI;4l1RE$Tv0l@t@Xp6YFQAoxGKJzr zN<{PUVM#0mKDFkTrj|dN;xriP)YIoXc|?qRFt?nTB*Y_Q`n9aaGG;&s=PVu?uAZ+0 zOqPnCi;3u0mashoS#F6X{a~&_Jd@8MlS$w**{6ghyR+yaOvEIyxK9yJkHv!uGAL#} zlZz&cn@`-%LNqKOJc(7n(wb+EQ<9629s?_5Ima`sAaJ>r>Aswv!vz zx2}iUc_29OtYShv*F{q()`1MlgbhMH@txtHqjjyk;U`~clW;!=#`Y=WH-?Hd?&u4Beu*+>g&TzG-jyoO-Z2Sco!TtT;H5uS+e*xu)w4?w4 diff --git a/maildir_gtd/actions/archive.py b/maildir_gtd/actions/archive.py index a72c49e..23ab991 100644 --- a/maildir_gtd/actions/archive.py +++ b/maildir_gtd/actions/archive.py @@ -12,9 +12,8 @@ def action_archive(app) -> None: text=True ) if result.returncode == 0: - app.query_one("#main_content", Static).update(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}.") + app.show_status(f"Error archiving message: {result.stderr}", "error") except Exception as e: - app.query_one("#main_content", Static).update(f"Error: {e}") + app.show_status(f"Error: {e}", "error") diff --git a/maildir_gtd/actions/delete.py b/maildir_gtd/actions/delete.py index fd8aa80..7991bb4 100644 --- a/maildir_gtd/actions/delete.py +++ b/maildir_gtd/actions/delete.py @@ -14,10 +14,9 @@ def action_delete(app) -> None: 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.") + app.query_one("#main_content").loading = False 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}.") + app.show_status(f"Failed to delete message {app.current_message_id}.", "error") except Exception as e: - app.query_one("#main_content", Static).update(f"Error: {e}") + app.show_status(f"Error: {e}", "error") diff --git a/maildir_gtd/actions/newest.py b/maildir_gtd/actions/newest.py new file mode 100644 index 0000000..d6da131 --- /dev/null +++ b/maildir_gtd/actions/newest.py @@ -0,0 +1,23 @@ + +import subprocess + +def action_newest(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", "-s", "9999"], + 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) + app.current_message_id = ids[0] + app.show_message(app.current_message_id) + return + + 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/next.py b/maildir_gtd/actions/next.py index 05ec6e2..4cef2d8 100644 --- a/maildir_gtd/actions/next.py +++ b/maildir_gtd/actions/next.py @@ -15,7 +15,7 @@ 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"], + ["himalaya", "envelope", "list", "-o", "json", "-s", "9999"], capture_output=True, text=True ) @@ -23,13 +23,13 @@ def action_next(app) -> None: import json envelopes = json.loads(result.stdout) ids = sorted(int(envelope['id']) for envelope in envelopes) - for index, envelope_id in enumerate(ids): + for envelope_id in ids: if envelope_id > int(app.current_message_id): app.show_message(envelope_id) return app.show_status("No newer messages found.", severity="warning") - app.show_message(envelopes[-1]['id']) # Automatically show the previous message + app.action_newest() else: app.show_status("Failed to fetch envelope list.", severity="error") except Exception as e: diff --git a/maildir_gtd/actions/oldest.py b/maildir_gtd/actions/oldest.py new file mode 100644 index 0000000..419aef5 --- /dev/null +++ b/maildir_gtd/actions/oldest.py @@ -0,0 +1,23 @@ + +import subprocess + +def action_oldest(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", "-s", "9999"], + 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)) + app.current_message_id = ids[0] + app.show_message(app.current_message_id) + return + + 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/previous.py b/maildir_gtd/actions/previous.py index 4e7ed49..f475d2c 100644 --- a/maildir_gtd/actions/previous.py +++ b/maildir_gtd/actions/previous.py @@ -1,11 +1,11 @@ -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"], + ["himalaya", "envelope", "list", "-o", "json", "-s", "9999"], capture_output=True, text=True ) @@ -19,6 +19,7 @@ def action_previous(app) -> None: app.show_message(app.current_message_id) return app.show_status("No older messages found.", severity="warning") + app.action_oldest() else: app.show_status("Failed to fetch envelope list.", severity="error") except Exception as e: diff --git a/maildir_gtd/actions/show_message.py b/maildir_gtd/actions/show_message.py index 9a01eca..10b7e72 100644 --- a/maildir_gtd/actions/show_message.py +++ b/maildir_gtd/actions/show_message.py @@ -1,23 +1,14 @@ -from textual.widgets import Static -from rich.markdown import Markdown +import logging import subprocess +from textual.logging import TextualHandler + + +logging.basicConfig( + level="NOTSET", + handlers=[TextualHandler()], +) def show_message(app, message_id: int) -> None: """Fetch and display the email message by ID.""" app.current_message_id = message_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 - 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}") + logging.info("Showing message ID: " + str(message_id)) diff --git a/maildir_gtd/app.py b/maildir_gtd/app.py index 5e46e62..927bb0c 100644 --- a/maildir_gtd/app.py +++ b/maildir_gtd/app.py @@ -1,5 +1,10 @@ +import re import sys import os +from datetime import datetime # Add this import at the top of the file + +from actions.newest import action_newest +from actions.oldest import action_oldest sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) import logging @@ -9,8 +14,8 @@ from textual.widget import Widget from textual.app import App, ComposeResult, SystemCommand, RenderResult 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.widgets import Header, Footer, Static, Label, Input, Button, Markdown +from textual.reactive import reactive, Reactive from textual.binding import Binding from textual.timer import Timer from textual.containers import ScrollableContainer, Horizontal, Vertical, Grid @@ -22,25 +27,24 @@ 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 +from maildir_gtd.widgets.EnvelopeHeader import EnvelopeHeader logging.basicConfig( level="NOTSET", handlers=[TextualHandler()], ) -class Hello(Widget): - """Display a greeting.""" - def render(self) -> RenderResult: - return "Hello, [b]World[/b]!" class StatusTitle(Static): - total_messages: Reactive[int] = Reactive(0) - current_message_index: Reactive[int] = Reactive(0) - current_message_id: Reactive[int] = Reactive(1) + total_messages: Reactive[int] = reactive(0) + current_message_index: Reactive[int] = reactive(0) + current_message_id: Reactive[int] = reactive(1) + folder: Reactive[str] = reactive("INBOX") def render(self) -> RenderResult: - return f"Inbox Message ID: {self.current_message_id} | [b]{self.current_message_index}[/b]/{self.total_messages}" + return f"{self.folder} | ID: {self.current_message_id} | [b]{self.current_message_index}[/b]/{self.total_messages}" + @@ -48,9 +52,10 @@ class StatusTitle(Static): class EmailViewerApp(App): """A simple email viewer app using the Himalaya CLI.""" title = "Maildir GTD Reader" - current_message_id: Reactive[int] = Reactive(1) + current_message_id: Reactive[int] = reactive(1) CSS_PATH = "email_viewer.tcss" - + folder = reactive("INBOX") + markdown: Reactive[str] = reactive("Loading...") def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]: yield from super().get_system_commands(screen) @@ -60,6 +65,8 @@ class EmailViewerApp(App): 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) + yield SystemCommand("Oldest Message", "Show the oldest message", self.action_oldest) + yield SystemCommand("Newest Message", "Show the newest message", self.action_newest) BINDINGS = [ Binding("j", "next", "Next message"), @@ -80,20 +87,36 @@ class EmailViewerApp(App): def compose(self) -> ComposeResult: """Create child widgets for the app.""" - yield Header(show_clock=True) + # yield Header(show_clock=True) yield StatusTitle().data_bind(EmailViewerApp.current_message_id) - yield ScrollableContainer(Static(Label("Loading Email Viewer App"), id="main_content")) + yield EnvelopeHeader() + yield Markdown(id="main_content", markdown=self.markdown) yield Footer() - def watch_current_message_id(self, message_id: int, old_message_id: int) -> None: + def watch_current_message_id(self, old_message_id: int, new_message_id: int) -> None: """Called when the current message ID changes.""" - if (message_id == old_message_id): + logging.info(f"Current message ID changed from {old_message_id} to {new_message_id}") + self.query_one("#main_content").loading = True + self.markdown = "" + if (new_message_id == old_message_id): return - # self.notify("Current message ID changed", title="Status", severity="information") - logging.info(f"Current message ID changed to {message_id}") + try: + rawText = subprocess.run( + ["himalaya", "message", "read", str(new_message_id)], + capture_output=True, + text=True + ) + if rawText.returncode == 0: + # Render the email content as Markdown + fixedText = rawText.stdout.replace("(https://urldefense.com/v3/", "(") + fixedText = re.sub(r"atlOrigin.+?\)", ")", fixedText) + self.query_one("#main_content").loading = False + self.query_one("#main_content").update(markdown = str(fixedText)) + logging.info(fixedText) + result = subprocess.run( - ["himalaya", "envelope", "list", "-o", "json"], + ["himalaya", "envelope", "list", "-o", "json", "-s", "9999"], capture_output=True, text=True ) @@ -102,40 +125,35 @@ class EmailViewerApp(App): envelopes = json.loads(result.stdout) if envelopes: status = self.query_one(StatusTitle) - status.total_messages = len(envelopes) # Get the first envelope's ID - status.current_message_index = next( - (index + 1 for index, envelope in enumerate(envelopes) if int(envelope['id']) == message_id), - 0 # Default to 0 if no match is found - ) + status.total_messages = len(envelopes) + + headers = self.query_one(EnvelopeHeader) + # Find the index of the envelope that matches the current_message_id + for index, envelope in enumerate(sorted(envelopes, key=lambda x: int(x['id']))): + if int(envelope['id']) == new_message_id: + headers.subject = envelope['subject'] + headers.from_ = envelope['from']['addr'] + headers.to = envelope['to']['addr'] + headers.date = datetime.strptime(envelope['date'].replace("+00:00", ""), "%Y-%m-%d %H:%M").strftime("%a %b %d %H:%M") + status.current_message_index = index + 1 # 1-based index + break + status.update() + headers.update() + else: - self.query_one("#main_content", Static).update("Failed to fetch the most recent message ID.") + self.query_one("#main_content").update("Failed to fetch the most recent message ID.") except Exception as e: - self.query_one("#main_content", Static).update(f"Error: {e}") + self.query_one("#main_content").update(f"Error: {e}") def on_mount(self) -> None: self.alert_timer: Timer | None = None # Timer to throttle alerts self.theme = "monokai" + self.title = "MaildirGTD" # self.watch(self.query_one(StatusTitle), "current_message_id", update_progress) # 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']) - 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) + self.action_oldest() def show_message(self, message_id: int) -> None: show_message(self, message_id) @@ -165,24 +183,30 @@ class EmailViewerApp(App): def action_scroll_down(self) -> None: """Scroll the main content down.""" - self.query_one("#main_content", Static).scroll_down() + self.query_one("#main_content").scroll_down() def action_scroll_up(self) -> None: """Scroll the main content up.""" - self.query_one("#main_content", Static).scroll_up() + self.query_one("#main_content").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() + self.query_one("#main_content").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() + self.query_one("#main_content").scroll_page_up() def action_quit(self) -> None: """Quit the application.""" self.exit() + def action_oldest(self) -> None: + action_oldest(self) + + def action_newest(self) -> None: + action_newest(self) + if __name__ == "__main__": app = EmailViewerApp() app.run() diff --git a/maildir_gtd/email_viewer.tcss b/maildir_gtd/email_viewer.tcss index 51138c8..93fffe5 100644 --- a/maildir_gtd/email_viewer.tcss +++ b/maildir_gtd/email_viewer.tcss @@ -18,6 +18,16 @@ StatusTitle { width: 100%; height: 1; color: $text; - background: $surface; + background: rgb(64, 62, 65); content-align: center middle; } + +EnvelopeHeader { + width: 100%; + height: auto; + background: $panel; +} + +#main_content { + padding: 1 2; +} diff --git a/maildir_gtd/widgets/EnvelopeHeader.py b/maildir_gtd/widgets/EnvelopeHeader.py new file mode 100644 index 0000000..99300a5 --- /dev/null +++ b/maildir_gtd/widgets/EnvelopeHeader.py @@ -0,0 +1,24 @@ +from textual.reactive import Reactive +from textual.app import RenderResult +from textual.widgets import Static, Label + +class EnvelopeHeader(Static): + + subject = Reactive("") + from_ = Reactive("") + to = Reactive("") + date = Reactive("") + + """Header for the email viewer.""" + def on_mount(self) -> None: + """Mount the header.""" + + def render(self) -> RenderResult: + return f"[b][dim]Subject:[/dim] {self.subject}[/] \r\n" \ + f"[dim]From:[/dim] {self.from_} \r\n" \ + f"[dim]To:[/dim] {self.to} \r\n" \ + f"[dim]Date:[/dim] {self.date}" + + + + diff --git a/maildir_gtd/widgets/__init__.py b/maildir_gtd/widgets/__init__.py new file mode 100644 index 0000000..e6ab631 --- /dev/null +++ b/maildir_gtd/widgets/__init__.py @@ -0,0 +1 @@ +# Initialize the screens subpackage diff --git a/maildir_gtd/widgets/__pycache__/EnvelopeHeader.cpython-311.pyc b/maildir_gtd/widgets/__pycache__/EnvelopeHeader.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b44a98a1357bc082a13b821f6914db87d7b0f7f6 GIT binary patch literal 1398 zcmZ`(J8u&~5T1M5NgR@pkWfU(JOa2N*8++tqVQC3;Q+-&(dpvdBu003*jw|kD2zmr zNGU8DkPuzOL-1o#T3bb;qB|5SDrW9-mJDXS_ub4l`=o(DT2)!g{yGn;$15bSY`vqG=)MIG zuExOCb>IfmK5DolK528!fvh!WEVPH2K}yt#oAswWXl(O4 z+{g44C|hvZ6!mhomjtFlHl=-JBVZ{toUFGd?l+osS(ID6xRp$wFMokAnld9EY(zAt z_`BjfjJSv#Sqs;(Leg2{>4w7Dl%rC;V}(L+Izem==TbMzJWg8 zuU_0lKRZWw4T&E0gVK@=P&JFkB!VdJUh2P6I+=Z+i>CX{$|AjOy%BWn*t8vMnOVl?nj{g=CiE1RH{q5~UmKw{L z<0~#milFP7s_R$$JHL=t@P;^pL+F&FCPHjzn%0H6gZ|!y3kUtZ3#EfWJvPTQ)b=lQ N9{nWt*MLx>_zyn1Q@8*C literal 0 HcmV?d00001 diff --git a/maildir_gtd/widgets/__pycache__/__init__.cpython-311.pyc b/maildir_gtd/widgets/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..814758d6d5960338837a9ed2bd0e4211443ecb09 GIT binary patch literal 185 zcmZ3^%ge<81j{E0WvBq@#~=<2FhUuh*?^4c3@Hr344RC7D;bKIfc(!O$zMMDp~b01 z#rh?gxk;&cDJA+Ysb#4-`30#(`spPpx+SSaxtV#1Il3kJ`8mbw5 z%#`%hl4AY%_{_Y_lK6PNg34bUHo5sJr8%i~MXW%>LDm=Z1BnmJjEsyQ7+^#ZGf)fw D>x?fY literal 0 HcmV?d00001