From b854981497ef21bc9f3b791c7a5e564a85f5f320 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Fri, 18 Nov 2022 22:30:07 -0800 Subject: [PATCH] UI edits: enter web site page, /docs, flashed messages, copy Bridgy's CSS --- common.py | 10 +- models.py | 18 +- pages.py | 45 ++++- static/style.css | 351 +++++++++++++++++++++++++++++----- templates/activities.html | 2 +- templates/base.html | 23 ++- templates/docs.html | 241 +++++++++++++++++++++++ templates/enter_web_site.html | 16 ++ templates/user.html | 19 ++ tests/test_models.py | 4 + 10 files changed, 661 insertions(+), 68 deletions(-) create mode 100644 templates/docs.html create mode 100644 templates/enter_web_site.html diff --git a/common.py b/common.py index 150cf42..4c6d10a 100644 --- a/common.py +++ b/common.py @@ -82,7 +82,8 @@ def requests_post(url, **kwargs): def _requests_fn(fn, url, parse_json=False, **kwargs): """Wraps requests.* and adds raise_for_status().""" - resp = fn(url, gateway=True, **kwargs) + kwargs.setdefault('gateway', True) + resp = fn(url, **kwargs) logger.info(f'Got {resp.status_code} headers: {resp.headers}') type = content_type(resp) @@ -449,13 +450,14 @@ def redirect_unwrap(val): return val -def actor(domain): +def actor(domain, user=None): """Fetches a home page, converts its representative h-card to AS2 actor. Creates a User for the given domain if one doesn't already exist. Args: domain: str + user: :class:`User`, optional Returns: dict, AS2 actor """ @@ -469,7 +471,9 @@ def actor(domain): if not hcard: error(f"Couldn't find a representative h-card (http://microformats.org/wiki/representative-hcard-parsing) on {mf2['url']}") - user = User.get_or_create(domain) + if not user: + user = User.get_or_create(domain) + actor = postprocess_as2( as2.from_as1(microformats2.json_to_object(hcard)), user=user) actor.update({ diff --git a/models.py b/models.py index 8e2b343..6edfee1 100644 --- a/models.py +++ b/models.py @@ -2,6 +2,7 @@ import logging import urllib.parse +import requests from werkzeug.exceptions import BadRequest, NotFound from Crypto.PublicKey import RSA @@ -79,18 +80,21 @@ class User(StringIdModel): # check webfinger redirect path = f'/.well-known/webfinger?resource=acct:{domain}@{domain}' - resp = common.requests_get(urllib.parse.urljoin(site, path), - allow_redirects=False) - expected = urllib.parse.urljoin(request.host_url, path) - if resp.is_redirect and resp.headers.get('Location') == expected: - self.has_redirects = True + try: + resp = common.requests_get(urllib.parse.urljoin(site, path), + allow_redirects=False, gateway=False) + expected = urllib.parse.urljoin(request.host_url, path) + self.has_redirects = (resp.is_redirect and + resp.headers.get('Location') == expected) + except requests.RequestException: + self.has_redirects = False # check home page try: - common.actor(self.key.id()) + common.actor(self.key.id(), user=self) self.has_hcard = True except (BadRequest, NotFound): - pass + self.has_hcard = False class Activity(StringIdModel): diff --git a/pages.py b/pages.py index f93254d..de4226e 100644 --- a/pages.py +++ b/pages.py @@ -9,7 +9,7 @@ from flask import redirect, render_template, request from google.cloud.ndb.stats import KindStat from granary import as2, atom, microformats2, rss from oauth_dropins.webutil import flask_util, logs, util -from oauth_dropins.webutil.flask_util import error +from oauth_dropins.webutil.flask_util import error, flash, redirect from oauth_dropins.webutil.util import json_dumps, json_loads from app import app, cache @@ -23,8 +23,40 @@ FOLLOWERS_UI_LIMIT = 999 @app.route('/') @flask_util.cached(cache, datetime.timedelta(days=1)) def front_page(): - """View for the front page.""" - return render_template('index.html') + """View for the front page.""" + return render_template('index.html') + + +@app.route('/docs') +@flask_util.cached(cache, datetime.timedelta(days=1)) +def docs(): + """View for the docs page.""" + return render_template('docs.html') + + +@app.get('/web-site') +@flask_util.cached(cache, datetime.timedelta(days=1)) +def enter_web_site(): + return render_template('enter_web_site.html') + + +@app.post('/web-site') +def check_web_site(): + url = request.values['url'] + domain = util.domain_from_link(url) + if not domain: + error(f'No domain found in {url}') + + user = User.get_or_create(domain) + try: + user.verify() + except BaseException as e: + if util.is_connection_failure(e): + flash(f"Couldn't connect to {url}") + return render_template('enter_web_site.html') + + user.put() + return redirect(f'/user/{domain}') @app.get(f'/responses/') # deprecated @@ -34,13 +66,14 @@ def user_deprecated(domain): @app.get(f'/user/') def user(domain): - if not User.get_by_id(domain): - return render_template('user_not_found.html', domain=domain), 404 + user = User.get_by_id(domain) + if not user: + return render_template('user_not_found.html', domain=domain), 404 query = Activity.query( Activity.status.IN(('new', 'complete', 'error')), Activity.domain == domain, - ) + ) activities, before, after = fetch_page(query, Activity) followers = Follower.query(Follower.dest == domain)\ diff --git a/static/style.css b/static/style.css index 604e726..c90d7f8 100644 --- a/static/style.css +++ b/static/style.css @@ -18,6 +18,48 @@ min-width: 30px; } +#messages { + position: fixed; + width: 100%; + font-size: 1.5em; + top: .2em; + text-align: center; + z-index: 999; +} + +.message { + display: block; + opacity: 1; + background-color: gold; + color: #444; + padding: .5em; + transition: opacity 5s linear 20s; +} + +#logins { + margin-top: .3em; + text-align: right; +} + +@media (max-width: 600px) { + #logo { + float: left; + } + #header { + float: none; + width: 100%; + } +} + +#signups { + margin-top: 2em; +} + +#signups hr { + width: 50%; + border-color: lightgray; +} + .big { font-size: 1.5em; } @@ -40,22 +82,157 @@ font-weight: normal; } +#more-link { + font-size: .7em; +} + +body.about #more-link { + display: none; +} + +pre .keyword, code .keyword, code.keyword { + color: green; +} + +pre .value, code .value, code.value { + color: chocolate; +} + .row { text-align: center; } -#top { +#blogposts, #publishes, #webmentions, #user { + margin-top: 1em; +} + +#top, #listen-ui, #listening-label, #publish-ui, #publishing-label { margin-bottom: 1em; } +/* tentatively removed since it didn't look great with webmention signups */ +/* @media (min-width: 768px) { */ +/* #listen-signup, #listen-ui { */ +/* border-left: 1px solid lightgray; */ +/* border-right: 1px solid lightgray; */ +/* } */ +/* } */ + +#listen-signups input, #webmention-signups input { + margin: .5em; +} + +.header, #user, .promo, #listen-signups, #webmention-signups { + margin-bottom: 1em; +} + +#edit-profile em { + font-style: normal; +} + +form { + display: inline; +} + +form input { + vertical-align: middle; +} + +.delete-website { + padding: 0; + font-size: .7em; + border: none; + background-color: transparent; +} + +.delete-website:hover { + color: red; + background-color: transparent; +} + +#users-label { + margin-top: 1em; +} + +#users { + list-style: none; +} + +#users-paging { + clear: both; +} + +.source { + text-align: left; + margin-top: .5em; +} + +#choose-blog > ul { + list-style: none; + text-align: left; +} + +@media (min-width: 768px) { + .user-items > .row { + display: table; + table-layout: fixed !important; + width: 100%; + text-align: left; + } + + .user-items > .row > .col-sm-1, + .user-items > .row > .col-sm-2, + .user-items > .row > .col-sm-3, + .user-items > .row > .col-sm-4 { + display: table-cell; + float: none; + text-align: left; + border-spacing: 0; + } +} + +@media (max-width: 767px) { + .user-items > .row { + margin-bottom: 1em; + } +} + +.source-buttons, .original-post { + white-space: nowrap; +} + +.delete { + border: none; + background: none; + font-weight: bold; + color: red; +} + +.delete:hover, .delete:focus { + color: darkred; +} + +/* .glyphicon-warning-sign { color: gold; } */ +.glyphicon-ok-sign { color: green; } +.glyphicon-exclamation-sign { color: gold; } +.glyphicon-pause { color: gold`; } +.glyphicon-refresh { color: blue; } +.glyphicon-remove { color: red !important; } +.glyphicon-transfer { color: blue; } + +.user-items { + font-size: .8em; + list-style: none; +} + code { color: black; background-color: white !important; - font-size: .8em; } -a code { - color: inherit; +.original-post-links { + list-style: none; + padding-left: 0; } label { @@ -63,16 +240,11 @@ label { font-weight: 300; } -#sites li { - display: inline; -} - -#sites li::before { - content: "\2022 \00A0"; -} - -#sites li:first-child::before { - content: none; +/* Seems like this shouldn't be necessary, but cursor was ending up as auto on the + * a > img for the disabled Facebook signup button for some reason, not sure why. + */ +a > img { + cursor: pointer; } button { @@ -86,7 +258,26 @@ button { border-radius: 2px; } +button[disabled] { + border-color: gray !important; + color: gray !important; + pointer-events: auto !important; +} + +button[disabled]:hover { + background-color: lightgray !important; +} + +#preview-ui { + margin-top: 1em; +} + +#preview-ui > * { + vertical-align: middle; +} + .btn-default { + background-color: #CEF; border-color: #337AB7; color: #337AB7; } @@ -97,15 +288,76 @@ button { color: #337AB7; } -#get-started { - border-color: green; - color: white; - background-color: #6A6; - font-size: 1.5em; +.disable-button, #bad-button { + border-color: red; + color: red; } -#get-started:hover { - background-color: #8C8; +.disable-button:hover, #bad-button:hover { + background-color: #FCC; +} + +#good-button { + border-color: green; + color: green; +} + +#good-button:hover { + background-color: #DED; +} + +#micropub-token-button, #disable-publish-button { + margin-top: -6px; +} + +.mastodon-button { + height: 50px; + padding: 6px; + background-color: #323946; +} + +#preview .verb { + font-weight: bold; +} + +#preview-text { + display: inline-block; + margin-top: 1em; + text-align: left; + white-space: pre-wrap; + font-family: inherit; + font-size: inherit; + max-width: 98%; +} + +#preview-text hr { + border-color: #cccccc; +} + +#preview-text img, #preview-text video { + max-height: 200px; + max-width: 100%; + margin-top: 1em; +} + +.mastodon-embed { + margin: 1em; +} + +input[type="text"], input[type="url"] { + padding-left: .3em; + padding-right: .3em; + border: 1px solid black; + font-size: 1em; +} + +#sent pre { + display: inline-block; +} + +/* Override the inline display:block which makes it align left instead of center. */ +iframe#twitter-widget-0 { + display: inline !important; } #footer { @@ -114,7 +366,7 @@ button { text-align: right; } -.shadow { +.shadow, .profile, #screenshot { -moz-box-shadow: 2px 2px 4px rgba(0,0,0,0.4); -webkit-box-shadow: 2px 2px 4px rgba(0,0,0,0.4); /* CSS3 */ @@ -125,7 +377,7 @@ button { filter: progid:DXImageTransform.Microsoft.Shadow(Strength=2,Direction=135,Color='#666666'); } -.shadow:hover { +.shadow:hover, .profile:hover, #screenshot:hover { -moz-box-shadow: 2px 2px 4px rgba(0,0,0,0.8); -webkit-box-shadow: 2px 2px 4px rgba(0,0,0,0.8); /* CSS3 */ @@ -136,7 +388,30 @@ button { filter: progid:DXImageTransform.Microsoft.Shadow(Strength=2,Direction=135,Color='#222222'); } -#docs > ul { +.error, .warning { + margin: 10px; + padding: .2em; + font-style: italic; +} + +.error { + background-color: lightcoral !important; +} + +.warning { + background-color: gold !important; +} + +.error p, .warning p { + margin: 10px; +} + +.error code, .warning code { + font-style: normal; + background-color: inherit !important; +} + +#docs { list-style: none; } @@ -146,34 +421,18 @@ button { font-weight: bold; } -.answer ul { +.answer ul li, .answer ol { margin-bottom: .5em; } -pre .keyword, code.keyword { - color: green; -} - -pre .value, code.value { - color: chocolate; -} - -td, th { - padding-left: .4em; - padding-right: .4em; - padding-top: .2em; - padding-bottom: .2em; +/* confirm_instagram.html */ +iframe { + width: 50%; + border: 1px solid gray; + height: 300px; } .follower { text-align: left; margin-top: .5em; } - -/* .glyphicon-warning-sign { color: gold; } */ -.glyphicon-ok-sign { color: green; } -.glyphicon-exclamation-sign { color: gold; } -.glyphicon-pause { color: gold; } -.glyphicon-refresh { color: blue; } -.glyphicon-remove { color: red !important; } -.glyphicon-transfer { color: blue; } diff --git a/templates/activities.html b/templates/activities.html index 221dae2..e5327b4 100644 --- a/templates/activities.html +++ b/templates/activities.html @@ -17,7 +17,7 @@ {% else %} -
None
+
Nothing yet!
{% endfor %} {% include "paging.html" %} diff --git a/templates/base.html b/templates/base.html index 42c4393..68bc008 100644 --- a/templates/base.html +++ b/templates/base.html @@ -13,10 +13,23 @@ + + +{% with messages = get_flashed_messages() %} +{% if messages %} +
+ {% for message in messages %} +

{{ message|safe }}

+ {% endfor %} +
+{% endif %} +{% endwith %} +

@@ -33,10 +46,9 @@ @@ -48,7 +60,8 @@ and the rest of the fediverse< {% endblock %}