initial commit
This commit is contained in:
commit
f50ba452df
13 changed files with 757 additions and 0 deletions
169
.gitignore
vendored
Executable file
169
.gitignore
vendored
Executable file
|
@ -0,0 +1,169 @@
|
|||
config.json
|
||||
|
||||
# Deployment Unix socket
|
||||
*.sock
|
||||
|
||||
# Temp files
|
||||
*~
|
||||
*swp
|
||||
*swo
|
||||
|
||||
# web
|
||||
node_modules/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# Mastodon secrets
|
||||
**/*.secret
|
61
README.md
Normal file
61
README.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
# Python Template
|
||||
|
||||
An opinionated starter project for flask python projects. It contains code for a new [Flask Blueprint]https://flask.palletsprojects.com/en/stable/blueprints/).
|
||||
|
||||
**DO NOT PUSH PROJECT-SPECIFIC CHANGES**
|
||||
|
||||
## Project setup
|
||||
|
||||
1. Set up your **Debian** (for other environments, search for counterpart instructions)
|
||||
|
||||
```bash
|
||||
# update repositories
|
||||
$ sudo apt update
|
||||
|
||||
# install python stuff
|
||||
$ sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools python3-venv
|
||||
```
|
||||
|
||||
> For MacOS: https://docs.python.org/3/using/mac.html
|
||||
|
||||
2. Install dependencies and set up the project
|
||||
|
||||
```bash
|
||||
# clone the project
|
||||
$ git clone git@git.sr.ht:~ayoayco/python-template [project-name]
|
||||
|
||||
# go into the directory
|
||||
$ cd [project-name]
|
||||
|
||||
# remove template .git stuff
|
||||
$ rm -rf .git
|
||||
|
||||
# initialize git
|
||||
$ git init .
|
||||
|
||||
# create python environment:
|
||||
$ python3 -m venv .venv
|
||||
|
||||
# activate python env:
|
||||
$ . .venv/bin/activate
|
||||
|
||||
# install dependencies
|
||||
(.venv)$ python -m pip install -r requirements.txt
|
||||
|
||||
# create configuration from example config file
|
||||
(.venv)$ cp ./example_config.json ./config.json
|
||||
|
||||
# rejoice!
|
||||
```
|
||||
|
||||
3. To start development, run the following:
|
||||
```bash
|
||||
(.venv)$ flask --debug run
|
||||
```
|
||||
|
||||
> Note: On a Mac, the default port 5000 is used by AirDrop & Handoff; you may have to turn those off
|
||||
|
||||
4. After development session, deactivate the python env
|
||||
``bash
|
||||
` (.venv)$ deactivate
|
||||
```
|
0
__init__.py
Normal file
0
__init__.py
Normal file
14
app.py
Executable file
14
app.py
Executable file
|
@ -0,0 +1,14 @@
|
|||
from flask import Flask
|
||||
import json
|
||||
from .blueprintname import blueprintname
|
||||
from .cache import cache
|
||||
|
||||
# TODO: Update blueprintname
|
||||
|
||||
app = Flask(__name__)
|
||||
cache.init_app(app, config={'CACHE_TYPE': 'SimpleCache'})
|
||||
app.register_blueprint(blueprintname, url_prefix='/')
|
||||
app.config.from_file("config.json", load=json.load)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0')
|
20
blueprintname.py
Executable file
20
blueprintname.py
Executable file
|
@ -0,0 +1,20 @@
|
|||
from flask import Blueprint, render_template, current_app
|
||||
from .cache import cache
|
||||
|
||||
blueprintname = Blueprint("blueprintname", __name__, template_folder="templates", static_folder="static")
|
||||
|
||||
|
||||
def get_attribution():
|
||||
return current_app.config['ATTRIBUTION']
|
||||
|
||||
def get_app_config():
|
||||
return current_app.config['APPS']['blueprintname']
|
||||
|
||||
|
||||
@blueprintname.route("/")
|
||||
@cache.cached(timeout=300)
|
||||
async def home():
|
||||
|
||||
# DO STUFF...
|
||||
|
||||
return render_template("_home.html", app=get_app_config(), attribution=get_attribution())
|
2
cache.py
Normal file
2
cache.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from flask_caching import Cache
|
||||
cache = Cache()
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
flask[async]
|
||||
Flask-Caching
|
29
static/button.css
Normal file
29
static/button.css
Normal file
|
@ -0,0 +1,29 @@
|
|||
.btn {
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
border: 1px solid;
|
||||
color: var(--text-color-dark);
|
||||
border-color: var(--border-color-light);
|
||||
background-color: var(--bg-light);
|
||||
padding: 0.25em 0.5em;
|
||||
font-weight: bold;
|
||||
font-size: var(--font-size-base);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-brand-blue-3);
|
||||
border-color: var(--color-brand-blue-3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.btn {
|
||||
color: var(--text-color-light);
|
||||
border-color: var(--border-color-dark);
|
||||
background-color: var(--bg-dark);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-brand-complement);
|
||||
border-color: var(--color-brand-complement);
|
||||
}
|
||||
}
|
||||
}
|
175
static/card.css
Normal file
175
static/card.css
Normal file
|
@ -0,0 +1,175 @@
|
|||
.card_content {
|
||||
& .invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .emoji {
|
||||
display: inline;
|
||||
height: calc(1rem + 6px);
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
& .ellipsis::after {
|
||||
content: "...";
|
||||
}
|
||||
|
||||
& .body {
|
||||
.hashtag:not(.pill),
|
||||
.mention:not(.pill) {
|
||||
color: var(--text-color-dark-faded);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
& code {
|
||||
font-size: var(--font-size-sm);
|
||||
background: rgb(245, 242, 240);
|
||||
padding: 0.25em 0.3em;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
vertical-align: text-bottom;
|
||||
overflow-x: auto;
|
||||
overflow-wrap: initial;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
& a:has(.link-card) {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
& .media,
|
||||
& .link-card {
|
||||
border: 1px solid;
|
||||
border-color: var(--border-color-light);
|
||||
color: var(--text-color-dark-faded);
|
||||
border-radius: 5px;
|
||||
box-shadow: 5px 25px 10px -25px rgba(34, 34, 34, 0.15);
|
||||
max-width: 100%;
|
||||
margin: 15px 0 1em;
|
||||
object-fit: contain;
|
||||
height: auto;
|
||||
text-decoration: none;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
& .media:hover,
|
||||
& .link-card:hover {
|
||||
color: var(--color-brand-blue-3);
|
||||
border: 1px solid var(--color-brand-blue-3);
|
||||
text-decoration-color: var(--color-link);
|
||||
}
|
||||
|
||||
& p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
& .heading {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
gap: 5px;
|
||||
height: 20px;
|
||||
|
||||
& .author {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
& .right_menu {
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: right;
|
||||
|
||||
& a,
|
||||
& span {
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
& a {
|
||||
color: var(--text-color-dark-faded);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-link);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .link-card {
|
||||
color: var(--text-color-dark-faded);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--text-color-light-faded);
|
||||
|
||||
& strong,
|
||||
& small {
|
||||
text-decoration-thickness: 1px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
article.card {
|
||||
/* border-bottom: 1px solid;
|
||||
border-color: var(--border-color-light);
|
||||
*/
|
||||
|
||||
& .bottom-menu {
|
||||
padding: 0.25em 0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card_content {
|
||||
& .action {
|
||||
color: var(--color-brand-complement);
|
||||
}
|
||||
|
||||
& .heading .right_menu a {
|
||||
color: var(--text-color-light-faded);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-brand-complement);
|
||||
}
|
||||
}
|
||||
|
||||
& .body {
|
||||
.hashtag:not(.pill),
|
||||
.mention:not(.pill) {
|
||||
color: var(--text-color-light-faded);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background: rgb(45, 51, 59);
|
||||
color: rgb(197, 209, 222);
|
||||
}
|
||||
|
||||
& .media,
|
||||
& .link-card {
|
||||
border: 1px solid;
|
||||
border-color: var(--border-color-dark);
|
||||
color: var(--text-color-light-faded);
|
||||
background-color: var(--bg-dark);
|
||||
}
|
||||
|
||||
& .media:hover,
|
||||
& .link-card:hover {
|
||||
color: var(--color-brand-complement);
|
||||
border: 1px solid var(--color-brand-complement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* article.card {
|
||||
border-color: var(--border-color-dark);
|
||||
} */
|
||||
}
|
78
static/reset.css
Normal file
78
static/reset.css
Normal file
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
THANKS TO JOSH COMEAU FOR HIS CUSTOM CSS RESET
|
||||
👉 https://www.joshwcomeau.com/css/custom-css-reset/
|
||||
**/
|
||||
|
||||
/*
|
||||
1. Use a more-intuitive box-sizing model.
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/*
|
||||
2. Remove default margin
|
||||
*/
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
/*
|
||||
3. Allow percentage-based heights in the application
|
||||
*/
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
/*
|
||||
Typographic tweaks!
|
||||
4. Add accessible line-height
|
||||
5. Improve text rendering
|
||||
*/
|
||||
body {
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
/*
|
||||
6. Improve media defaults
|
||||
*/
|
||||
img,
|
||||
picture,
|
||||
video,
|
||||
canvas,
|
||||
svg,
|
||||
iframe {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
/*
|
||||
7. Remove built-in form typography styles
|
||||
*/
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
/*
|
||||
8. Avoid text overflows
|
||||
*/
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
/*
|
||||
9. Create a root stacking context
|
||||
*/
|
||||
#root,
|
||||
#__next {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
|
31
static/variables.css
Normal file
31
static/variables.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
:root {
|
||||
--content-width: 700px;
|
||||
--font-size-sm: clamp(0.75rem, 0.2vw + 0.66rem, 0.8rem);
|
||||
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
||||
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
||||
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
||||
|
||||
--color-border: hsl(17, 24%, 90%);
|
||||
--color-link: var(--color-brand-blue-5);
|
||||
|
||||
--color-brand-blue-1: #3054bf;
|
||||
--color-brand-blue-2: #203880;
|
||||
--color-brand-blue-3: #416fff;
|
||||
--color-brand-blue-4: #101c40;
|
||||
--color-brand-blue-5: #3964e6;
|
||||
|
||||
--color-brand-complement: orange;
|
||||
|
||||
--ayo-gradient: linear-gradient(45deg, #3054bf, #416fff);
|
||||
--text-color-dark: #232323;
|
||||
--text-color-dark-faded: #555;
|
||||
--text-color-light: #f8f9fa;
|
||||
--text-color-light-faded: #999;
|
||||
|
||||
--border-color-light: rgba(197, 209, 222, 0.7);
|
||||
--bg-light: rgba(197, 209, 222, 0.15);
|
||||
--border-color-dark: rgba(0, 0, 0, 0.25);
|
||||
--bg-dark: #343a40;
|
||||
--bg-darker: #212529;
|
||||
--bg-darkest: #000;
|
||||
}
|
175
templates/_home.html
Normal file
175
templates/_home.html
Normal file
|
@ -0,0 +1,175 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{ app.title }}</title>
|
||||
<meta name="theme-color" content="#3054bf">
|
||||
<meta name="description" content="{{ app.description }}" />
|
||||
<meta property="og:description" content="{{ app.description }}" />
|
||||
<meta name="author" content="{{ attribution.owner }}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="{{ app.site_name }}" />
|
||||
<meta property="og:title" content="{{ app.title }}" />
|
||||
|
||||
<style>
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
color: var(--text-color-dark);
|
||||
font-size: var(--font-size-base);
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
padding: 0 1em;
|
||||
|
||||
a {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
& a.app-title {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header,
|
||||
footer {
|
||||
background: var(--ayo-gradient);
|
||||
color: var(--text-color-light);
|
||||
border-radius: 5px;
|
||||
padding: 1em;
|
||||
text-wrap: balance;
|
||||
|
||||
& a {
|
||||
color: var(--text-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
& ul.tags {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
|
||||
& li {
|
||||
display: inline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
main.home {
|
||||
& .back {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
main.thread {
|
||||
& .card:not(:last-of-type) .card_avatar::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-right: 2px solid rgba(34, 34, 34, 0.15);
|
||||
width: 26px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
.card_avatar img {
|
||||
border: 2px solid rgba(197, 209, 222, 0.15);
|
||||
border-radius: 50%;
|
||||
display: inline;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.card {
|
||||
grid-template-columns: 55px auto;
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
html,
|
||||
body {
|
||||
background: var(--bg-darker);
|
||||
color: var(--text-color-light);
|
||||
}
|
||||
|
||||
main a {
|
||||
color: var(--color-brand-complement);
|
||||
}
|
||||
|
||||
main.thread {
|
||||
& .card:not(:last-of-type) .card_avatar::after {
|
||||
border-right: 2px solid rgba(197, 209, 222, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Use parent app's variables & reset stylesheets -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='variables.css') }}" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='reset.css') }}" />
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a id="top"></a>
|
||||
<header>
|
||||
{% include "nav.html" %}
|
||||
<a class="app-title" href="{{url_for('blueprintname.home')}}">
|
||||
<h1>{{ app.title }}</h1>
|
||||
</a>
|
||||
<p>{{ app.description }}</p>
|
||||
|
||||
</header>
|
||||
<main>
|
||||
<p>CONTENT GOES HERE</p>
|
||||
</main>
|
||||
<footer>
|
||||
<p>
|
||||
Copyright ©
|
||||
{% if attribution.current_year %}
|
||||
{{ attribution.year}}-{{ attribution.current_year }}
|
||||
{% else %}
|
||||
{{ attribution.year}}
|
||||
{% endif %}
|
||||
{{ attribution.owner }}
|
||||
</p>
|
||||
<p>Rendered on {{ render_date }} in Europe/Amsterdam</p>
|
||||
</footer>
|
||||
|
||||
<script type="module">
|
||||
import TimeAgo from "https://esm.sh/v135/@github/relative-time-element@4.4.0/es2022/relative-time.js"
|
||||
customElements.define('relative-time', TimeAgo)
|
||||
</script>
|
||||
<script type="module" src="{{ url_for('blueprintname.static', filename='enhance-content.js') }}">
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
1
templates/nav.html
Normal file
1
templates/nav.html
Normal file
|
@ -0,0 +1 @@
|
|||
<nav><a href="/">Go home</a></nav>
|
Loading…
Reference in a new issue