Compare commits
12 Commits
7c600dcfba
...
fbec869257
| Author | SHA1 | Date | |
|---|---|---|---|
| fbec869257 | |||
| e9e3cb30a4 | |||
| a5e762c36b | |||
| bbcb01f8d1 | |||
| df0e66ad08 | |||
| 1fefc149e2 | |||
| 449cb13dbd | |||
| f206485124 | |||
| b185ecfe81 | |||
| 274b4065e2 | |||
| 85b6fbabf3 | |||
| 32cbf47d95 |
@@ -14,6 +14,8 @@ import json
|
||||
import threading
|
||||
import traceback
|
||||
import time
|
||||
import datetime
|
||||
import humanize
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
import settings
|
||||
@@ -39,8 +41,15 @@ def new_id():
|
||||
nid = gen_rand_id()
|
||||
return nid
|
||||
|
||||
|
||||
def fromnow(ts):
|
||||
return humanize.naturaltime(datetime.datetime.fromtimestamp(ts))
|
||||
|
||||
|
||||
build_folder = './build'
|
||||
|
||||
flask_app = Flask(__name__, template_folder=build_folder, static_folder=build_folder, static_url_path='')
|
||||
flask_app.jinja_env.filters['fromnow'] = fromnow
|
||||
cors = CORS(flask_app)
|
||||
|
||||
@flask_app.route('/api')
|
||||
@@ -156,11 +165,18 @@ def story(sid):
|
||||
@flask_app.route('/')
|
||||
@flask_app.route('/search')
|
||||
def index():
|
||||
stories_json = database.get_stories(settings.FEED_LENGTH, 0)
|
||||
stories = [json.loads(s) for s in stories_json]
|
||||
for s in stories:
|
||||
url = urlparse(s.get('url') or s.get('link') or '').hostname or ''
|
||||
s['hostname'] = url.replace('www.', '')
|
||||
|
||||
return render_template('index.html',
|
||||
title='QotNews',
|
||||
url='news.t0.vc',
|
||||
description='Hacker News, Reddit, Lobsters, and Tildes articles rendered in reader mode',
|
||||
robots='index',
|
||||
stories=stories,
|
||||
)
|
||||
|
||||
@flask_app.route('/<sid>', strict_slashes=False)
|
||||
@@ -171,9 +187,9 @@ def static_story(sid):
|
||||
except NotFound:
|
||||
pass
|
||||
|
||||
story = database.get_story(sid)
|
||||
if not story: return abort(404)
|
||||
story = json.loads(story.full_json)
|
||||
story_obj = database.get_story(sid)
|
||||
if not story_obj: return abort(404)
|
||||
story = json.loads(story_obj.full_json)
|
||||
|
||||
score = story['score']
|
||||
num_comments = story['num_comments']
|
||||
@@ -182,7 +198,7 @@ def static_story(sid):
|
||||
score, 's' if score != 1 else '',
|
||||
num_comments, 's' if num_comments != 1 else '',
|
||||
source)
|
||||
url = urlparse(story['url']).hostname or urlparse(story['link']).hostname or ''
|
||||
url = urlparse(story.get('url') or story.get('link') or '').hostname or ''
|
||||
url = url.replace('www.', '')
|
||||
|
||||
return render_template('index.html',
|
||||
@@ -190,6 +206,8 @@ def static_story(sid):
|
||||
url=url,
|
||||
description=description,
|
||||
robots='noindex',
|
||||
story=story,
|
||||
show_comments=request.path.endswith('/c'),
|
||||
)
|
||||
|
||||
http_server = WSGIServer(('', 33842), flask_app)
|
||||
|
||||
@@ -35,29 +35,103 @@
|
||||
overflow-y: scroll;
|
||||
}
|
||||
body {
|
||||
background: #000;
|
||||
}
|
||||
.nojs {
|
||||
color: white;
|
||||
max-width: 32rem;
|
||||
background: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="nojs">
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app because it's written in React.
|
||||
I was planning on writing a server-side version, but I've become distracted
|
||||
by other projects -- sorry!
|
||||
<br/>
|
||||
I originally wrote this for myself, and of course I whitelist JavaScript on
|
||||
all my own domains.
|
||||
<br/><br/>
|
||||
Alternatively, try activex.news.t0.vc for an ActiveX™ version.
|
||||
</noscript>
|
||||
<noscript></noscript>
|
||||
</div>
|
||||
<div id="root">
|
||||
<div class="container menu">
|
||||
<p>
|
||||
<a href="/">QotNews</a>
|
||||
<br />
|
||||
<span class="slogan">Hacker News, Reddit, Lobsters, and Tildes articles rendered in reader mode.</span>
|
||||
</p>
|
||||
</div>
|
||||
{% if story %}
|
||||
<div class="{% if show_comments %}container{% else %}article-container{% endif %}">
|
||||
<div class="article">
|
||||
<h1>{{ story.title }}</h1>
|
||||
|
||||
{% if show_comments %}
|
||||
<div class="info">
|
||||
<a href="/{{ story.id }}">View article</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="info">
|
||||
Source: <a class="source" href="{{ story.url or story.link }}">{{ url }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="info">
|
||||
{{ story.score }} points
|
||||
by <a href="{{ story.author_link }}">{{ story.author }}</a>
|
||||
{{ story.date | fromnow }}
|
||||
on <a href="{{ story.link }}">{{ story.source }}</a> |
|
||||
<a href="/{{ story.id }}/c">
|
||||
{{ story.num_comments }} comment{{ 's' if story.num_comments != 1 }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if not show_comments and story.text %}
|
||||
<div class="story-text">{{ story.text | safe }}</div>
|
||||
{% elif show_comments %}
|
||||
{% macro render_comment(comment, level) %}
|
||||
<div class="comment{% if level > 0 %} lined{% endif %}">
|
||||
<div class="info">
|
||||
<p>
|
||||
{% if comment.author == story.author %}[OP] {% endif %}{{ comment.author or '[Deleted]' }} | <a href="#{{ comment.author }}{{ comment.date }}" id="{{ comment.author }}{{ comment.date }}">{{ comment.date | fromnow }}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="text">{{ (comment.text | safe) if comment.text else '<p>[Empty / deleted comment]</p>' }}</div>
|
||||
{% for reply in comment.comments %}
|
||||
{{ render_comment(reply, level + 1) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
<div class="comments">
|
||||
{% for comment in story.comments %}{{ render_comment(comment, 0) }}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class='dot toggleDot'>
|
||||
<div class='button'>
|
||||
<a href="/{{ story.id }}{{ '/c' if not show_comments else '' }}">
|
||||
{{ '' if not show_comments else '' }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% elif stories %}
|
||||
<div class="container">
|
||||
{% for story in stories %}
|
||||
<div class='item'>
|
||||
<div class='title'>
|
||||
<a class='link' href='/{{ story.id }}'>
|
||||
<img class='source-logo' src='/logos/{{ story.source }}.png' alt='{{ story.source }}:' /> {{ story.title }}
|
||||
</a>
|
||||
<span class='source'>
|
||||
(<a class='source' href='{{ story.url or story.link }}'>{{ story.hostname }}</a>)
|
||||
</span>
|
||||
</div>
|
||||
<div class='info'>
|
||||
{{ story.score }} points
|
||||
by <a href="{{ story.author_link }}">{{ story.author }}</a>
|
||||
{{ story.date | fromnow }}
|
||||
on <a href="{{ story.link }}">{{ story.source }}</a> |
|
||||
<a class="{{ 'hot' if story.num_comments > 99 else '' }}" href="/{{ story.id }}/c">
|
||||
{{ story.num_comments }} comment{{ 's' if story.num_comments != 1 }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
Reference in New Issue
Block a user