diff --git a/Pipfile b/Pipfile index fcc6a70..84d2ab2 100644 --- a/Pipfile +++ b/Pipfile @@ -13,6 +13,7 @@ textual = "*" pydantic = "*" pymastodon = {version="*", index="moerks"} markdownify = "*" +pillow = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index c774c75..a53b2e3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0ff61328a9369d70ce7094e72d664495cbce99af0e1d0a0f6a81528b966a9ec5" + "sha256": "0c92ab9c7331b4c4ef3202f85b81a76f25fc007fb7d6cbf33eb6bc4882e1929d" }, "pipfile-spec": 6, "requires": { @@ -199,6 +199,81 @@ "markers": "python_version >= '3.7'", "version": "==0.1.2" }, + "pillow": { + "hashes": [ + "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c", + "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2", + "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb", + "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d", + "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa", + "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3", + "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1", + "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a", + "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd", + "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8", + "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999", + "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599", + "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936", + "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375", + "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d", + "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b", + "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60", + "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572", + "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3", + "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced", + "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f", + "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b", + "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19", + "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f", + "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d", + "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383", + "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795", + "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355", + "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57", + "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09", + "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b", + "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462", + "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf", + "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f", + "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a", + "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad", + "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9", + "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d", + "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45", + "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994", + "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d", + "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338", + "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463", + "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451", + "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591", + "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c", + "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd", + "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32", + "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9", + "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf", + "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5", + "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828", + "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3", + "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5", + "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2", + "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b", + "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2", + "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475", + "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3", + "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb", + "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef", + "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015", + "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002", + "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170", + "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84", + "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57", + "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f", + "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27", + "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a" + ], + "index": "pypi", + "version": "==10.3.0" + }, "pydantic": { "hashes": [ "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5", @@ -302,11 +377,11 @@ }, "pymastodon": { "hashes": [ - "sha256:290e12ba161a4c87374ac55a437ee78edf3d0d1dc6d65ab653ceebdd0cba2dfb", - "sha256:32526b5da307ad6f64ca5ff028437d23de07e4237e04fc0111ca8522b6f89fd5" + "sha256:3c4c054e65368c7c4786fc48d97a64efcced3c6eb95084ee852ccccfc8d04d81", + "sha256:7ae4df3e669a749f257a5cd8c6866d9331cf81412f31539dda31e17748493452" ], "index": "moerks", - "version": "==0.8.0" + "version": "==0.16.0" }, "requests": { "hashes": [ @@ -342,11 +417,11 @@ }, "textual": { "hashes": [ - "sha256:5c8c3322308e2b932c4550b0ae9f70daebc39716de3f920831cda96d1640b383", - "sha256:9daddf713cb64d186fa1ae647fea482dc84b643c9284132cd87adb99cd81d638" + "sha256:3a01be0b583f2bce38b8e9786b75ed33dddc816bba502d8e7a9ca3ca2ead3957", + "sha256:9902ebb4b00481f6fdb0e7db821c007afa45797d81e1d0651735a07de25ece87" ], "index": "pypi", - "version": "==0.58.0" + "version": "==0.58.1" }, "typing-extensions": { "hashes": [ diff --git a/src/mastotui.py b/src/mastotui.py index b1e928f..603c4fa 100644 --- a/src/mastotui.py +++ b/src/mastotui.py @@ -1,5 +1,6 @@ from textual.app import App, ComposeResult from textual.widgets import Header, Footer, ListView +from textual.containers import Container from renderers.status import StatusListItemRenderer from mastodon.application import MastodonApplication from mastodon.model.status import Status @@ -12,17 +13,21 @@ class Mastotui(App): logger.add("mastotui.log", level="DEBUG", rotation="100 MB") logger.remove(0) + CSS_PATH="mastotui.tcss" BINDINGS = [("H", "home", "Home"),("N", "notifications", "Notifications"),("R", "refresh", "Refresh")] renderer = StatusListItemRenderer() mastodon_app = MastodonApplication("mastotui") statuses = [] - def compose(self) -> ComposeResult: - yield Header() - yield ListView(id="status_list_view") - yield Footer() + def compose(self) -> ComposeResult: + yield Container( + Header(classes="app_header"), + ListView(id="status_list_view"), + Footer(), + id="dialog" + ) def action_home(self) -> None: self.notify("HOME", timeout=5) @@ -31,19 +36,30 @@ class Mastotui(App): def action_refresh(self) -> None: list_view = self.app.query_one("#status_list_view") - self.notify("Refresh", timeout=5) - response = self.mastodon_app.timelines.home_timeline() - self.statuses.clear() - for status in response: - try: - logger.trace(status) - self.statuses.append(Status.model_validate(status)) - except Exception as ve: - logger.debug(status) - logger.error(ve) + if len(self.statuses) == 0: + response = self.mastodon_app.timelines.home() + for status in response: + try: + logger.trace(status) + self.statuses.append(Status.model_validate(status)) + except Exception as ve: + logger.debug(status) + logger.error(ve) + else: + response = self.mastodon_app.timelines.home(since_id=self.statuses[0].status_id) + refresh_list = [] + for status in response: + try: + logger.trace(status) + refresh_list.append(Status.model_validate(status)) + except Exception as ve: + logger.debug(status) + logger.error(ve) + self.statuses = refresh_list + self.statuses list_view.clear() list_view.extend(self.renderer.render_toots(self.statuses)) + self.notify("Refreshed Home Timeline", timeout=3) def on_list_view_selected(self, event: ListView.Selected ) -> None: logger.debug("Select Status Id: {}".format(event.item.status.status_id)) diff --git a/src/mastotui.tcss b/src/mastotui.tcss new file mode 100644 index 0000000..f1ac66a --- /dev/null +++ b/src/mastotui.tcss @@ -0,0 +1,212 @@ +$polar_night_darkest: #2e3440; +$polar_night_dark: #3b4252; +$polar_night_light: #434c5e; +$polar_night_lightest: #4c566a; + +$snow_storm_dark: #d8dee9; +$snow_storm: #e5e9f0; +$snow_storm_light: #eceff4; + +$frost_lightest: #8fbcbb; +$frost_light: #88c0d0; +$frost_dark: #81a1c1; +$frost_darkest: #5e81ac; + +$aurora_red: #bf616a; +$aurora_orange: #d08770; +$aurora_yellow: #ebcb8b; +$aurora_green: #a3be8c; +$aurora_purple: #b48ead; + +StatusScreen { + align: center middle; +} + +StatusScreen > Container { + width: 80%; + height: auto; + border: heavy $snow_storm_dark; +} + +StatusScreen > Container > StatusFooter { + background: $snow_storm; + height: 1w; +} +.status_footer_key { + background: $snow_storm_dark; + color: $frost_darkest; + padding-left: 1; + padding-right: 1; +} +.status_footer_action { + background: $snow_storm; + color: $frost_darkest; + padding-left: 1; + padding-right: 1; +} + +StatusScreen > Container > StatsBar { + background: $polar_night_light; + height: 1w; +} +.status_header_account { + background: $polar_night_light; + color: $frost_light; +} +StatusListItem .stats_bar { + background: $polar_night_light; + height: 1w; + margin-left: 1 +} +.stats_bar_replies { + background: $polar_night_light; + color: $aurora_purple; + text-style: bold; + align-horizontal: left; + padding-left: 1; + padding-right: 1; +} +.stats_bar_replies_value { + background: $polar_night_light; + color: $aurora_green; + text-style: bold; + align-horizontal: left; + padding-left: 1; + padding-right: 1; +} +.stats_bar_reblogs { + background: $polar_night_light; + color: $frost_lightest; + color: $aurora_purple; + text-style: bold; + align-horizontal: left; + padding-left: 1; + padding-right: 1; +} +.stats_bar_reblogs_value { + background: $polar_night_light; + color: $frost_lightest; + color: $aurora_green; + text-style: bold; + align-horizontal: left; + padding-left: 1; + padding-right: 1; +} +.stats_bar_favourites { + background: $polar_night_light; + color: $frost_lightest; + color: $aurora_purple; + text-style: bold; + align-horizontal: left; + padding-left: 1; + padding-right: 1; +} +.stats_bar_favourites_value { + background: $polar_night_light; + color: $frost_lightest; + color: $aurora_green; + text-style: bold; + align-horizontal: left; + padding-left: 1; + padding-right: 1; +} + +.time_separator { + background: $polar_night_lightest; + margin-left: 1; + width: 100%; + padding: 0; + margin: 0; + margin-left: 1; + text-align: right; +} + +.zoom_status_header { + color: $frost_darkest; + background: $polar_night_dark; + text-style: bold; + height: auto; + width: 100%; +} + +.zoom_account_header { + background: $polar_night_light; + color: $frost_light; + text-style: bold; + height: auto; + width: 100%; +} + +.zoom_status_content { + background: $polar_night_darkest; + width: 100%; + padding: 0; + margin: 0; +} + +.status_header { + color: $frost_darkest; + background: $polar_night_dark; + text-style: bold; + height: auto; + width: 100%; + margin-left: 1; +} + +.account_header { + background: $polar_night_light; + color: $frost_light; + text-style: bold; + height: auto; + width: 100%; + margin-left: 1; +} + +.status_content { + background: $polar_night_darkest; + width: 100%; + padding: 0; + margin: 0; + margin-left: 1; +} +.status_stats { + background: $polar_night_darkest; + height: auto; + width: 100%; + margin-left: 1; +} +.status_replies { + color: $frost_lightest; + align-horizontal: left; + padding-left: 1; + width: auto; +} +.status_boosts { + color: $frost_lightest; + align-horizontal: left; + padding-left: 2; + width: auto; +} +.status_favourites { + color: $frost_lightest; + align-horizontal: left; + padding-left: 2; + width: auto; +} +.status_time { + color: $polar_night_lightest; + align-horizontal: right; + padding-left: 10; +} + +Footer { + background: $snow_storm; +} +.footer--description { + background: $snow_storm; + color: $frost_darkest; +} +.footer--key { + background: $snow_storm_dark; + color: $frost_darkest; +} diff --git a/src/screens/status.py b/src/screens/status.py index b00bc1f..6363e80 100644 --- a/src/screens/status.py +++ b/src/screens/status.py @@ -1,8 +1,13 @@ from textual.app import ComposeResult -from textual.containers import Container +from textual.containers import Container, Horizontal from textual.screen import ModalScreen -from textual.widgets import Label +from textual.widgets import Label, Markdown, Button from loguru import logger +from markdownify import markdownify +from PIL import Image +import requests + +from widgets.status import StatsBar, StatusFooter class StatusScreen(ModalScreen): @@ -10,26 +15,106 @@ class StatusScreen(ModalScreen): super().__init__() self.status = status - BINDINGS = [("q", "back", "Go Back")] - - DEFAULT_CSS = """ - StatusScreen { - align: center middle; - } - - StatusScreen > Container { - width: auto; - height: auto; - } - """ + CSS_PATH = "../mastotui.tcss" + BINDINGS = [ + ("q", "back", "Close"), + ("b", "boost", "Boost"), + ("f", "favourite", "Favourite"), + ("1", "media_1", "1st Media"), + ("2", "media_2", "2nd Media"), + ("3", "media_3", "3rd Media"), + ("4", "media_4", "4th Media") + ] def compose(self) -> ComposeResult: + if self.status.reblog is not None: + status_header = "{} ({}) Boosted".format(self.status.account.display_name, self.status.account.acct) + display_name = self.status.reblog.account.display_name + acct = self.status.reblog.account.acct + content = self.status.reblog.content + created_at = self.status.reblog.created_at + replies = self.status.reblog.replies_count + boosts = self.status.reblog.reblogs_count + favourites = self.status.reblog.favourites_count + self.media_attachments = self.status.reblog.media_attachments + else: + status_header = "" + display_name = self.status.account.display_name + acct = self.status.account.acct + content = self.status.content + created_at = self.status.created_at + replies = self.status.replies_count + boosts = self.status.reblogs_count + favourites = self.status.favourites_count + self.media_attachments = self.status.media_attachments + + main_bindings = [("q","Close"), + ("b", "Boost"), + ("f", "Favourite"), + ("r", "Reply")] + variable_bindings = [] + for index, attachment in enumerate(self.media_attachments): + if index == 0: + variable_bindings.append(("1", "1st Media")) + if index == 1: + variable_bindings.append(("2", "2nd Media")) + if index == 2: + variable_bindings.append(("3", "3rd Media")) + if index == 3: + variable_bindings.append(("4", "4th Media")) + + + with Container(): - yield(Label(self.status.status_id)) + yield Label(status_header, classes="zoom_status_header") + yield Label("{} ({})".format(display_name, acct), classes="zoom_account_header") + yield Markdown(markdownify(content), classes="zoom_status_content") + yield StatsBar(replies, boosts, favourites) + yield StatusFooter(main_bindings, variable_bindings) + def action_back(self) -> None: - logger.debug("Screen Stack {}".format(len(self.app.screen_stack))) if len(self.app.screen_stack) >= 1: self.app.pop_screen() + def action_media_1(self) -> None: + if len(self.media_attachments) > 0: + logger.debug("Opening {} with url: {}".format(self.media_attachments[0].media_type, self.media_attachments[0].url)) + if self.media_attachments[0].media_type == "image": + image = Image.open(requests.get(self.media_attachments[0].url, stream=True).raw) + image.show() + def action_media_2(self) -> None: + if len(self.media_attachments) > 1: + logger.debug("Opening {}".format(self.media_attachments[1].url)) + if self.media_attachments[1].media_type == "image": + image = Image.open(requests.get(self.media_attachments[1].url, stream=True).raw) + image.show() + + + def action_media_3(self) -> None: + if len(self.media_attachments) > 2: + logger.debug("Opening {}".format(self.media_attachments[2].url)) + if self.media_attachments[2].media_type == "image": + image = Image.open(requests.get(self.media_attachments[2].url, stream=True).raw) + image.show() + + + def action_media_4(self) -> None: + if len(self.media_attachments) > 3: + logger.debug("Opening {}".format(self.media_attachments[3].url)) + if self.media_attachments[3].media_type == "image": + image = Image.open(requests.get(self.media_attachments[3].url, stream=True).raw) + image.show() + + + def build_footer_value(self, media_attachments): + if len(media_attachments) > 0: + footer_value = "q to go back" + for index, media in enumerate(media_attachments): + footer_value = footer_value + " | [{}] Show Media".format(index+1) + return footer_value + + else: + return "q to go back" + diff --git a/src/widgets/status.py b/src/widgets/status.py index 10bee99..ab11571 100644 --- a/src/widgets/status.py +++ b/src/widgets/status.py @@ -1,9 +1,57 @@ from textual.app import ComposeResult +from textual.containers import Horizontal +from textual.widget import Widget from textual.widgets import ListItem, Label, Markdown from mastodon.model.status import Status from loguru import logger from markdownify import markdownify +class StatusFooter(Widget): + def __init__(self, main_bindings, variable_bindings): + super().__init__() + self.bindings = main_bindings + variable_bindings + + def compose(self) -> ComposeResult: + binding_list = [] + for binding in self.bindings: + binding_list.append(Label(binding[0], classes="status_footer_key")) + binding_list.append(Label(binding[1], classes="status_footer_action")) + yield Horizontal(*binding_list, classes="status_footer") + +class StatusHeader(Widget): + def __init__(self, account_display_name = None, account_acct = None, boost_display_name = None, boost_acct = None): + super().__init__() + self.account_display_name = account_display_name + self.account_acct = account_acct + self.boost_display_name = boost_display_name + self.boost_acct = boost_acct + + def compose(self) -> ComposeResult: + accounts = [] + if self.boost_display_name is not None and self.boost_acct is not None: + accounts.append(Label("{} ({}) Boosted -> ".format(self.boost_display_name, self.boost_acct), classes="status_header_boost")) + accounts.append(Label("{} ({})".format(self.account_display_name, self.account_acct), classes="status_header_account")) + yield Horizontal(*accounts, classes="status_header") + + +class StatsBar(Widget): + def __init__(self, replies, reblogs, favourites): + super().__init__() + self.replies = replies + self.reblogs = reblogs + self.favourites = favourites + + def compose(self) -> ComposeResult: + yield Horizontal( + Label("Replies", classes="stats_bar_replies"), + Label("{}".format(self.replies), classes="stats_bar_replies_value"), + Label("Boosts", classes="stats_bar_reblogs"), + Label("{}".format(self.reblogs), classes="stats_bar_reblogs_value"), + Label("Favourited".format(self.favourites), classes="stats_bar_favourites"), + Label("{}".format(self.favourites), classes="stats_bar_favourites_value"), + classes="stats_bar" + ) + class StatusListItem(ListItem): def __init__(self, status: Status): super().__init__() @@ -11,16 +59,31 @@ class StatusListItem(ListItem): def compose(self) -> ComposeResult: logger.trace("ID: {} {}".format(self.status.status_id, self.status.content)) - yield Label(self.status.status_id) - status_header = "" - content = "" - if self.status.content == "": - status_header = "{} ({}) Reblogged".format(self.status.account.display_name, self.status.account.acct) +# yield Label(self.status.status_id) + boost_display_name = None + boost_acct = None + display_name = None + acct = None + if self.status.reblog is not None: + boost_display_name = self.status.account.display_name + boost_acct = self.status.account.acct + display_name = self.status.reblog.account.display_name + acct = self.status.reblog.account.acct content = self.status.reblog.content - yield Label(status_header, id="status_header") - yield Label(self.status.reblog.account.display_name, id="account_header") + created_at = self.status.reblog.created_at + replies = self.status.reblog.replies_count + boosts = self.status.reblog.reblogs_count + favourites = self.status.reblog.favourites_count else: - yield Label("{} ({})".format(self.status.account.display_name, self.status.account.acct), id="account_header") + display_name = self.status.account.display_name + acct = self.status.account.acct content = self.status.content + created_at = self.status.created_at + replies = self.status.replies_count + boosts = self.status.reblogs_count + favourites = self.status.favourites_count - yield Markdown(markdownify(content), id="status_content") + yield StatusHeader(display_name, acct, boost_display_name, boost_acct) + yield Markdown(markdownify(content), classes="status_content") + yield StatsBar(replies, boosts, favourites) + yield Label("{}".format(created_at), classes="time_separator")