Added better support for PWA manifest detection and a default /manifest.json

This commit is contained in:
Fabio Manganiello 2023-04-16 01:44:44 +02:00
parent bc11db3647
commit 463e846448
2 changed files with 119 additions and 47 deletions

View file

@ -1,84 +1,125 @@
import os import os
from typing import Optional from typing import Optional
from flask import request, Response, send_from_directory as send_from_directory_, render_template from flask import (
jsonify,
request,
Response,
send_from_directory as send_from_directory_,
render_template,
)
from .app import app from .app import app
from .config import config from .config import config
from ._sorters import PagesSortByTimeGroupedByFolder from ._sorters import PagesSortByTimeGroupedByFolder
def send_from_directory(path: str, file: str, alternative_path: Optional[str] = None, *args, **kwargs): def send_from_directory(
path: str, file: str, alternative_path: Optional[str] = None, *args, **kwargs
):
if not os.path.exists(os.path.join(path, file)) and alternative_path: if not os.path.exists(os.path.join(path, file)) and alternative_path:
path = alternative_path path = alternative_path
return send_from_directory_(path, file, *args, **kwargs) return send_from_directory_(path, file, *args, **kwargs)
@app.route('/', methods=['GET']) @app.route("/", methods=["GET"])
def home_route(): def home_route():
return render_template( return render_template(
'index.html', "index.html",
pages=app.get_pages(sorter=PagesSortByTimeGroupedByFolder), pages=app.get_pages(sorter=PagesSortByTimeGroupedByFolder),
config=config config=config,
) )
@app.route('/img/<img>', methods=['GET']) @app.route("/img/<img>", methods=["GET"])
def img_route(img: str): def img_route(img: str):
return send_from_directory(app.img_dir, img, config.default_img_dir) return send_from_directory(app.img_dir, img, config.default_img_dir)
@app.route('/favicon.ico', methods=['GET']) @app.route("/favicon.ico", methods=["GET"])
def favicon_route(): def favicon_route():
return img_route('favicon.ico') return img_route("favicon.ico")
@app.route('/js/<file>', methods=['GET']) @app.route("/js/<file>", methods=["GET"])
def js_route(file: str): def js_route(file: str):
return send_from_directory(app.js_dir, file, config.default_js_dir) return send_from_directory(app.js_dir, file, config.default_js_dir)
@app.route('/pwabuilder-sw.js', methods=['GET']) @app.route("/pwabuilder-sw.js", methods=["GET"])
def pwa_builder_route(): def pwa_builder_route():
return send_from_directory(app.js_dir, 'pwabuilder-sw.js', config.default_js_dir) return send_from_directory(app.js_dir, "pwabuilder-sw.js", config.default_js_dir)
@app.route('/pwabuilder-sw-register.js', methods=['GET']) @app.route("/pwabuilder-sw-register.js", methods=["GET"])
def pwa_builder_register_route(): def pwa_builder_register_route():
return send_from_directory(app.js_dir, 'pwabuilder-sw-register.js', config.default_js_dir) return send_from_directory(
app.js_dir, "pwabuilder-sw-register.js", config.default_js_dir
)
@app.route('/css/<style>', methods=['GET']) @app.route("/css/<style>", methods=["GET"])
def css_route(style: str): def css_route(style: str):
return send_from_directory(app.css_dir, style, config.default_css_dir) return send_from_directory(app.css_dir, style, config.default_css_dir)
@app.route('/fonts/<file>', methods=['GET']) @app.route("/fonts/<file>", methods=["GET"])
def fonts_route(file: str): def fonts_route(file: str):
return send_from_directory(app.fonts_dir, file, config.default_fonts_dir) return send_from_directory(app.fonts_dir, file, config.default_fonts_dir)
@app.route('/manifest.json', methods=['GET']) @app.route("/manifest.json", methods=["GET"])
def manifest_route(): def manifest_route():
return send_from_directory(config.content_dir, 'manifest.json') # If there is a manifest.json in the content directory, use it
manifest_file = os.path.join(config.content_dir, "manifest.json")
if os.path.isfile(manifest_file):
return send_from_directory(config.content_dir, "manifest.json")
# Otherwise, generate a default manifest.json
return jsonify(
{
"name": config.title,
"short_name": config.title,
"icons": [
{"src": "/img/icon-48.png", "sizes": "48x48", "type": "image/png"},
{"src": "/img/icon-72.png", "sizes": "72x72", "type": "image/png"},
{"src": "/img/icon-96.png", "sizes": "96x96", "type": "image/png"},
{"src": "/img/icon-144.png", "sizes": "144x144", "type": "image/png"},
{"src": "/img/icon-168.png", "sizes": "168x168", "type": "image/png"},
{"src": "/img/icon-192.png", "sizes": "192x192", "type": "image/png"},
{"src": "/img/icon-256.png", "sizes": "256x256", "type": "image/png"},
{"src": "/img/icon-512.png", "sizes": "512x512", "type": "image/png"},
],
"gcm_sender_id": "",
"gcm_user_visible_only": True,
"start_url": "/",
"permissions": ["gcm"],
"scope": "",
"orientation": "portrait",
"display": "standalone",
"theme_color": "#ffffff",
"background_color": "#000000",
}
)
@app.route('/article/<path:path>/<article>', methods=['GET']) @app.route("/article/<path:path>/<article>", methods=["GET"])
def article_with_path_route(path: str, article: str): def article_with_path_route(path: str, article: str):
return app.get_page(os.path.join(path, article)) return app.get_page(os.path.join(path, article))
@app.route('/article/<article>', methods=['GET']) @app.route("/article/<article>", methods=["GET"])
def article_route(article: str): def article_route(article: str):
return article_with_path_route('', article) return article_with_path_route("", article)
@app.route('/rss', methods=['GET']) @app.route("/rss", methods=["GET"])
def rss_route(): def rss_route():
pages = app.get_pages(with_content=True, skip_header=True, skip_html_head=True) pages = app.get_pages(with_content=True, skip_header=True, skip_html_head=True)
short_description = 'short' in request.args short_description = "short" in request.args
return Response('''<?xml version="1.0" encoding="UTF-8" ?> return Response(
"""<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/"> <rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
<channel> <channel>
<title>{title}</title> <title>{title}</title>
@ -95,18 +136,20 @@ def rss_route():
{items} {items}
</channel> </channel>
</rss>'''.format( </rss>""".format(
title=config.title, title=config.title,
description=config.description, description=config.description,
link=config.link, link=config.link,
categories=','.join(config.categories), categories=",".join(config.categories),
language=config.language, language=config.language,
last_pub_date=( last_pub_date=(
pages[0][1]['published'].strftime('%a, %d %b %Y %H:%M:%S GMT') pages[0][1]["published"].strftime("%a, %d %b %Y %H:%M:%S GMT")
if pages else '' if pages
), else ""
items='\n\n'.join([ ),
''' items="\n\n".join(
[
"""
<item> <item>
<title>{title}</title> <title>{title}</title>
<link>{base_link}{link}</link> <link>{base_link}{link}</link>
@ -114,17 +157,26 @@ def rss_route():
<description><![CDATA[{content}]]></description> <description><![CDATA[{content}]]></description>
<media:content medium="image" url="{base_link}{image}" width="200" height="150" /> <media:content medium="image" url="{base_link}{image}" width="200" height="150" />
</item> </item>
'''.format( """.format(
base_link=config.link, base_link=config.link,
title=page.get('title', '[No Title]'), title=page.get("title", "[No Title]"),
link=page.get('uri', ''), link=page.get("uri", ""),
published=page['published'].strftime('%a, %d %b %Y %H:%M:%S GMT') if 'published' in page else '', published=page["published"].strftime(
content=page.get('description', '') if short_description else page.get('content', ''), "%a, %d %b %Y %H:%M:%S GMT"
image=page.get('image', ''), )
) if "published" in page
for _, page in pages else "",
]), content=page.get("description", "")
), mimetype='application/xml') if short_description
else page.get("content", ""),
image=page.get("image", ""),
)
for _, page in pages
]
),
),
mimetype="application/xml",
)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -2,12 +2,32 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- PWA & Viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="viewport" content="uc-fitscreen=yes"/>
<meta name="description" content="{{ config.description }}">
<link rel="manifest" href="/manifest.json">
<!-- Android PWA -->
<meta name="theme-color" content="white">
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="{{ config.title }}">
<!-- iOS PWA -->
<meta name="apple-mobile-web-app-title" content="{{ config.title }}">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<!-- Layout mode -->
<meta name="layoutmode" content="fitscreen/standard">
<!-- Orientation -->
<meta name="screen-orientation" content="portrait">
<!-- RSS feed -->
<link rel="alternate" type="application/rss+xml" title="{{ config.title }}" href="/rss" /> <link rel="alternate" type="application/rss+xml" title="{{ config.title }}" href="/rss" />
<!-- Fonts & Styles -->
<link rel="stylesheet" href="/fonts/poppins.css"> <link rel="stylesheet" href="/fonts/poppins.css">
<link rel="stylesheet" href="/fonts/fira-sans.css"> <link rel="stylesheet" href="/fonts/fira-sans.css">
<link rel="stylesheet" href="/css/common.css"> <link rel="stylesheet" href="/css/common.css">
<link rel="manifest" href="/manifest.json"> <!-- PWA builder -->
<script type="module" src="/pwabuilder-sw-register.js"></script> <script type="module" src="/pwabuilder-sw-register.js"></script>
{% if styles %} {% if styles %}