diff --git a/src/small/models/nodes.py b/src/small/models/nodes.py index 50b5f00..29bcc1d 100644 --- a/src/small/models/nodes.py +++ b/src/small/models/nodes.py @@ -16,9 +16,23 @@ class Image: height: int +@dataclass +class IFrame: + src: str + width: int + height: int + + +@dataclass +class GithubGist: + id: str + filename: str = None + content: str = None + + @dataclass class Paragraph: - children: List[Union[Text, Image]] + children: List[Union[Text, Image, IFrame, GithubGist]] @dataclass diff --git a/src/small/services/github_client.py b/src/small/services/github_client.py new file mode 100644 index 0000000..305efed --- /dev/null +++ b/src/small/services/github_client.py @@ -0,0 +1,16 @@ +import requests + + +class GithubClient: + @staticmethod + def get_gist(gist_id): + url = f"https://api.github.com/gists/{gist_id}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + files = data["files"] + return { + filename: file_data["content"] for filename, file_data in files.items() + } + else: + return None diff --git a/src/small/services/medium_client.py b/src/small/services/medium_client.py index 90444ec..e3b66e6 100644 --- a/src/small/services/medium_client.py +++ b/src/small/services/medium_client.py @@ -2,7 +2,7 @@ import requests from flask import url_for -from small.models.nodes import Page, Paragraph, Text, Image +from small.models.nodes import Page, Paragraph, Text, Image, IFrame, GithubGist from datetime import datetime @@ -30,6 +30,14 @@ class MediumClient: originalWidth originalHeight } + iframe { + mediaResource { + href + iframeSrc + iframeWidth + iframeHeight + } + } } } } @@ -42,6 +50,9 @@ class MediumClient: response = requests.post(url, json={"query": query}) data = response.json()["data"]["post"] + if not data: + return None + paragraphs = [] for p in data["content"]["bodyModel"]["paragraphs"]: if p["type"] == "IMG": @@ -57,8 +68,21 @@ class MediumClient: height=p["metadata"]["originalHeight"], ) ] + elif p["type"] == "IFRAME": + iframe = p["iframe"]["mediaResource"] + if "gist.github.com" in iframe["href"]: + gist_id = iframe["href"].split("/")[-1] + children = [GithubGist(id=gist_id)] + else: + children = [ + IFrame( + src=iframe["iframeSrc"] or iframe["href"], + width=iframe["iframeWidth"], + height=iframe["iframeHeight"], + ) + ] else: - children = [Text(content=p["text"].strip(), type=p["type"])] + children = [Text(content=p["text"], type=p["type"])] paragraphs.append(Paragraph(children=children)) return Page( diff --git a/src/small/static/css/style.css b/src/small/static/css/style.css index c21ffc9..6906d5a 100644 --- a/src/small/static/css/style.css +++ b/src/small/static/css/style.css @@ -72,11 +72,39 @@ article img { margin: 20px 0; } +/* Code Block Styles */ pre { background-color: #f4f4f4; - padding: 10px; - border-left: 3px solid #1a73e8; - overflow-x: auto; + border: 1px solid #ddd; + border-left: 3px solid #f36d33; + color: #666; + page-break-inside: avoid; + font-family: monospace; + font-size: 15px; + line-height: 1.6; + margin-bottom: 1.6em; + max-width: 100%; + overflow: auto; + padding: 1em 1.5em; + display: block; + word-wrap: break-word; +} + +/* IFrame Styles */ +iframe { + max-width: 100%; + border: none; + margin: 20px 0; +} + +/* GitHub Gist Styles */ +.gist { + margin: 20px 0; +} + +.gist h4 { + margin-bottom: 10px; + color: #333; } /* Error Page Styles */ diff --git a/src/small/templates/article.html b/src/small/templates/article.html index ad679a2..c9a8353 100644 --- a/src/small/templates/article.html +++ b/src/small/templates/article.html @@ -14,6 +14,18 @@

{{ child.alt }}

+ {% elif child.__class__.__name__ == 'IFrame' %} + + {% elif child.__class__.__name__ == 'GithubGist' %} + {% if child.content %} + {% for filename, content in child.content.items() %} +

{{ filename }}

+
{{ content }}
+ {% endfor %} + View Gist on GitHub + {% else %} +

Failed to load GitHub Gist

+ {% endif %} {% endif %} {% endfor %} {% endfor %} diff --git a/src/small/views/article.py b/src/small/views/article.py index f52aa08..ce2484f 100644 --- a/src/small/views/article.py +++ b/src/small/views/article.py @@ -1,6 +1,8 @@ from flask import Blueprint, render_template, abort +from werkzeug.exceptions import NotFound from small.services.medium_client import MediumClient +from small.services.github_client import GithubClient from small.utils.parse_article_id import parse_article_id bp = Blueprint("articles", __name__) @@ -14,7 +16,21 @@ def article(article_url): try: page = MediumClient.get_post(article_id) + + if not page: + abort(404) + + for paragraph in page.content: + for child in paragraph.children: + if child.__class__.__name__ == "GithubGist": + gist_id = child.id + gist = GithubClient.get_gist(gist_id) + child.content = gist + return render_template("article.html", page=page) except Exception as e: + if isinstance(e, NotFound): + raise + print(f"Error fetching article: {str(e)}") abort(500)