pratapvardhan commited on
Commit
4711c0c
·
0 Parent(s):

v0: Hackernews clone ft. FastHTML (read-only)

Browse files
Files changed (6) hide show
  1. .gitignore +151 -0
  2. Dockerfile +6 -0
  3. README.md +19 -0
  4. main.py +78 -0
  5. requirements.txt +3 -0
  6. style.css +30 -0
.gitignore ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .quarto
2
+ .sesskey
3
+ *.db-*
4
+ *.db
5
+ .gitattributes
6
+ _proc/
7
+ sidebar.yml
8
+ Gemfile.lock
9
+ token
10
+ _docs/
11
+ conda/
12
+ .last_checked
13
+ .gitconfig
14
+ *.bak
15
+ *.log
16
+ *~
17
+ ~*
18
+ _tmp*
19
+ tmp*
20
+ tags
21
+
22
+ # Byte-compiled / optimized / DLL files
23
+ __pycache__/
24
+ *.py[cod]
25
+ *$py.class
26
+
27
+ # C extensions
28
+ *.so
29
+
30
+ # Distribution / packaging
31
+ .Python
32
+ env/
33
+ build/
34
+ develop-eggs/
35
+ dist/
36
+ downloads/
37
+ eggs/
38
+ .eggs/
39
+ lib/
40
+ lib64/
41
+ parts/
42
+ sdist/
43
+ var/
44
+ wheels/
45
+ *.egg-info/
46
+ .installed.cfg
47
+ *.egg
48
+
49
+ # PyInstaller
50
+ # Usually these files are written by a python script from a template
51
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
52
+ *.manifest
53
+ *.spec
54
+
55
+ # Installer logs
56
+ pip-log.txt
57
+ pip-delete-this-directory.txt
58
+
59
+ # Unit test / coverage reports
60
+ htmlcov/
61
+ .tox/
62
+ .coverage
63
+ .coverage.*
64
+ .cache
65
+ nosetests.xml
66
+ coverage.xml
67
+ *.cover
68
+ .hypothesis/
69
+
70
+ # Translations
71
+ *.mo
72
+ *.pot
73
+
74
+ # Django stuff:
75
+ *.log
76
+ local_settings.py
77
+
78
+ # Flask stuff:
79
+ instance/
80
+ .webassets-cache
81
+
82
+ # Scrapy stuff:
83
+ .scrapy
84
+
85
+ # Sphinx documentation
86
+ docs/_build/
87
+
88
+ # PyBuilder
89
+ target/
90
+
91
+ # Jupyter Notebook
92
+ .ipynb_checkpoints
93
+
94
+ # pyenv
95
+ .python-version
96
+
97
+ # celery beat schedule file
98
+ celerybeat-schedule
99
+
100
+ # SageMath parsed files
101
+ *.sage.py
102
+
103
+ # dotenv
104
+ .env
105
+
106
+ # virtualenv
107
+ .venv
108
+ venv/
109
+ ENV/
110
+
111
+ # Spyder project settings
112
+ .spyderproject
113
+ .spyproject
114
+
115
+ # Rope project settings
116
+ .ropeproject
117
+
118
+ # mkdocs documentation
119
+ /site
120
+
121
+ # mypy
122
+ .mypy_cache/
123
+
124
+ .vscode
125
+ *.swp
126
+
127
+ # osx generated files
128
+ .DS_Store
129
+ .DS_Store?
130
+ .Trashes
131
+ ehthumbs.db
132
+ Thumbs.db
133
+ .idea
134
+
135
+ # pytest
136
+ .pytest_cache
137
+
138
+ # tools/trust-doc-nbs
139
+ docs_src/.last_checked
140
+
141
+ # symlinks to fastai
142
+ docs_src/fastai
143
+ tools/fastai
144
+
145
+ # link checker
146
+ checklink/cookies.txt
147
+
148
+ # .gitconfig is now autogenerated
149
+ .gitconfig
150
+
151
+ _docs
Dockerfile ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+ WORKDIR /code
3
+ COPY --link --chown=1000 . .
4
+ RUN pip install --no-cache-dir -r requirements.txt
5
+ ENV PYTHONUNBUFFERED=1 PORT=7860
6
+ CMD ["python", "main.py"]
README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hackernews clone ft. FastHTML (read-only)
2
+
3
+ This is a lightweight, read-only clone of Hacker News built with FastHTML. It provides a simple interface to browse Hacker News stories and comments.
4
+
5
+ ## Features
6
+
7
+ - View top stories on the home page
8
+ - Infinite scroll to load more stories
9
+ - View individual story pages with comments
10
+ - Responsive design
11
+
12
+ ## Contributing
13
+
14
+ Contributions are welcome! Please feel free to submit a Pull Request.
15
+
16
+ ## Acknowledgements
17
+
18
+ - Hacker News: https://news.ycombinator.com/
19
+ - Unofficial Hacker News API: https://github.com/cheeaun/node-hnapi
main.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fasthtml.common import *
2
+ from fastcore.net import urlparse
3
+ import httpx
4
+
5
+ app, rt = fast_app(pico=False, hdrs=(Link(href='style.css', rel='stylesheet'),))
6
+
7
+ async def fetch_get(url):
8
+ async with httpx.AsyncClient() as client:
9
+ response = await client.get(url)
10
+ response.raise_for_status()
11
+ return response.json()
12
+
13
+ def NavTop(back=False):
14
+ return Div(
15
+ Div(
16
+ Div(
17
+ Img(src="https://news.ycombinator.com/y18.svg", cls="logo"),
18
+ A(B('Hacker News'), href='./'),
19
+ A(NotStr('&nbsp;&nbsp;<< back'), href="./") if back else None,
20
+ cls='header-content'
21
+ ),
22
+ A('code', href="https://github.com/pratapvardhan/hackernews-fasthtml"),
23
+ cls='header-content space-between'),
24
+ cls='header')
25
+
26
+ def HomeRow(data, n='', pl=True):
27
+ domain = urlparse(data['url']).netloc
28
+ link = f'./item?id={data["id"]}'
29
+ return Div(
30
+ Div(
31
+ Span(f'{n}.' if n else None),
32
+ A(data['title'], href=data['url'], cls='st-t'),
33
+ Span(f'({domain})', cls='st-d') if domain else None,
34
+ cls='st'),
35
+ Div(
36
+ f'{data["points"]} points by', A(data['user']),
37
+ A(data['time_ago'], href=link), ' | ', A(f'{data["comments_count"]} comments', href=link),
38
+ cls=f"st-sub {'pl-15p' if pl else ''}"),
39
+ cls='st'
40
+ )
41
+
42
+ def Comment(data):
43
+ return Div(
44
+ Div(f"• {data.get('user', '[deleted]')} {data['time_ago']}", cls='st-sub'),
45
+ Div(NotStr(data['content']), cls='st-c'),
46
+ *map(Comment, data['comments']),
47
+ cls='st-cb',
48
+ style=f"margin-left: {'20px' if data['level'] else 0};",
49
+ )
50
+
51
+ def ScrollMore(page):
52
+ return Div('Scroll for more..', hx_get=f"/?page={page}", hx_trigger="intersect once", hx_swap="outerHTML", hx_target="this", cls='st-c')
53
+
54
+ async def Feed(page=1, top=True):
55
+ sno = (page - 1) * 30 + 1
56
+ data = await fetch_get(f"https://node-hnapi.herokuapp.com/news?page={page}")
57
+ return *[HomeRow(d, n) for n, d in enumerate(data, sno)], ScrollMore(page+1)
58
+
59
+ @rt("/")
60
+ async def get(page: int = 1):
61
+ content = await Feed(page, top=(page == 1))
62
+ return Div(NavTop(), Div(*content, cls='content'), cls='container') if page == 1 else content
63
+
64
+ @rt("/item")
65
+ async def get(id:int):
66
+ data = await fetch_get(f"https://node-hnapi.herokuapp.com/item/{id}")
67
+ return Div(
68
+ NavTop(back=True),
69
+ Div(
70
+ HomeRow(data, n='', pl=False),
71
+ Div(NotStr(data.get('content', '')), cls='st-g'), Br(),
72
+ *map(Comment, data['comments']),
73
+ style='padding:0 15px;'
74
+ ),
75
+ cls='container'
76
+ )
77
+
78
+ serve()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ python-fasthtml
2
+ uvicorn>=0.29
3
+ httpx
style.css ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: Verdana, Geneva, sans-serif;
3
+ font-size: 10pt;
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+ .container {
8
+ max-width: 796px;
9
+ padding: 10px;
10
+ margin: auto;
11
+ color: #000000;
12
+ background-color: #f6f6ef;
13
+ }
14
+ a { text-decoration: none; }
15
+ .space-between { justify-content: space-between; }
16
+ .header { background-color: #ff6600; padding: 2px; color: #000000; }
17
+ .header-content { display: flex; align-items: center; }
18
+ .logo { border: 1px solid white; margin-right: 5px; }
19
+ .content { padding: 0 8px; }
20
+ p { margin-top: 8px; margin-bottom: 4px; }
21
+ .st { margin: 8px 0 2px 0; }
22
+ .st-t, .header a { color: #000000; }
23
+ .st-d { font-size: 8pt; }
24
+ .st-c { font-size: 9pt; }
25
+ .st-cb { border-top: 1px solid gray; margin-top: 8px; padding-top: 8px; }
26
+ .st-sub { font-size: 7pt; }
27
+ .pl-15p { padding-left: 15px; }
28
+ .st-d, .st-sub, .st-d a, .st-sub a, .st-g { color: #828282; }
29
+ .st-sub a:hover { text-decoration: underline; }
30
+ .st-c a:link { color: #000000; text-decoration: underline; }