Compare commits
4 Commits
754aada478
...
de2ffd022a
Author | SHA1 | Date | |
---|---|---|---|
de2ffd022a | |||
e844936a00 | |||
dcc28483e3 | |||
1b6536d8ae |
179
main.py
179
main.py
@ -8,6 +8,7 @@ import render
|
||||
import secrets
|
||||
import nginx
|
||||
import threading
|
||||
import nostr as nostr_module
|
||||
|
||||
app = Flask(__name__)
|
||||
dotenv.load_dotenv()
|
||||
@ -50,6 +51,10 @@ if not os.path.isdir('certs'):
|
||||
def send_report(path):
|
||||
return send_from_directory('templates/assets', path)
|
||||
|
||||
@app.route('/https.js')
|
||||
def httpsJS():
|
||||
return send_from_directory('templates', 'https.js')
|
||||
|
||||
@app.route('/favicon.png')
|
||||
def faviconPNG():
|
||||
return send_from_directory('templates/assets/img', 'favicon.png')
|
||||
@ -64,7 +69,7 @@ def index():
|
||||
for i in cookies:
|
||||
if i['cookie'] == auth:
|
||||
return render_template('index.html',varo="window.location = '/site';", year=datetime.datetime.now().year)
|
||||
return render_template('index.html',varo=render.varo_login(), year=datetime.datetime.now().year)
|
||||
return render_template('index.html',varo=render.hnslogin(), year=datetime.datetime.now().year)
|
||||
# Remove any ports
|
||||
host = request.host.split(':')[0]
|
||||
# Get content from site
|
||||
@ -72,7 +77,7 @@ def index():
|
||||
with open(f'sites/{host}.json') as file:
|
||||
data = json.load(file)
|
||||
|
||||
return render.site(data, True)
|
||||
return render.site(data, host)
|
||||
return redirect(f'https://{DOMAINS[0]}')
|
||||
|
||||
@app.route('/site')
|
||||
@ -109,7 +114,8 @@ def site():
|
||||
"btn_bg": "#2c54cf",
|
||||
"btn_fg": "#ffffff",
|
||||
"socials": [],
|
||||
"address": []
|
||||
"address": [],
|
||||
"nostrs": []
|
||||
}
|
||||
|
||||
|
||||
@ -298,6 +304,104 @@ def publish():
|
||||
response.set_cookie('auth', '', expires=0)
|
||||
return response
|
||||
|
||||
@app.route('/nostr')
|
||||
def nostr():
|
||||
if 'auth' not in request.cookies:
|
||||
return redirect('/')
|
||||
auth = request.cookies['auth']
|
||||
|
||||
for i in cookies:
|
||||
if i['cookie'] == auth:
|
||||
# Load site content
|
||||
if os.path.isfile(f'sites/{i["name"]}.json'):
|
||||
with open(f'sites/{i["name"]}.json') as file:
|
||||
data = json.load(file)
|
||||
nostr = []
|
||||
if 'nostr' in data:
|
||||
nostr = data['nostr']
|
||||
|
||||
return render_template('nostr.html',year=datetime.datetime.now().year, domain=i['name'],nostr=nostr)
|
||||
|
||||
response = make_response(redirect('/'))
|
||||
response.set_cookie('auth', '', expires=0)
|
||||
return response
|
||||
|
||||
@app.route('/nostr', methods=['POST'])
|
||||
def nostr_post():
|
||||
if 'auth' not in request.cookies:
|
||||
return redirect('/')
|
||||
auth = request.cookies['auth']
|
||||
|
||||
for i in cookies:
|
||||
if i['cookie'] == auth:
|
||||
# Get site content
|
||||
if os.path.isfile(f'sites/{i["name"]}.json'):
|
||||
with open(f'sites/{i["name"]}.json') as file:
|
||||
data = json.load(file)
|
||||
else:
|
||||
return redirect('/site')
|
||||
|
||||
nostr = []
|
||||
if 'nostr' in data:
|
||||
nostr = data['nostr']
|
||||
|
||||
# Check for new nostr links
|
||||
if 'new-name' in request.form and 'new-pub' in request.form:
|
||||
name = request.form['new-name']
|
||||
pub = request.form['new-pub']
|
||||
id = len(nostr)
|
||||
for link in nostr:
|
||||
if link['name'] == name:
|
||||
link['pub'] = pub
|
||||
data['nostr'] = nostr
|
||||
with open(f'sites/{i["name"]}.json', 'w') as file:
|
||||
json.dump(data, file)
|
||||
return redirect('/nostr')
|
||||
if link['id'] >= id:
|
||||
id = link['id'] + 1
|
||||
|
||||
nostr.append({'name': name, 'pub': pub, 'id': id})
|
||||
|
||||
|
||||
data['nostr'] = nostr
|
||||
with open(f'sites/{i["name"]}.json', 'w') as file:
|
||||
json.dump(data, file)
|
||||
return redirect('/nostr')
|
||||
|
||||
response = make_response(redirect('/'))
|
||||
response.set_cookie('auth', '', expires=0)
|
||||
return response
|
||||
|
||||
@app.route('/nostr/delete/<int:id>')
|
||||
def nostr_delete(id):
|
||||
if 'auth' not in request.cookies:
|
||||
return redirect('/')
|
||||
auth = request.cookies['auth']
|
||||
|
||||
for i in cookies:
|
||||
if i['cookie'] == auth:
|
||||
# Get site content
|
||||
if os.path.isfile(f'sites/{i["name"]}.json'):
|
||||
with open(f'sites/{i["name"]}.json') as file:
|
||||
data = json.load(file)
|
||||
else:
|
||||
return redirect('/site')
|
||||
|
||||
nostr = []
|
||||
if 'nostr' in data:
|
||||
nostr = data['nostr']
|
||||
|
||||
nostr = [i for i in nostr if i['id'] != id]
|
||||
data['nostr'] = nostr
|
||||
with open(f'sites/{i["name"]}.json', 'w') as file:
|
||||
json.dump(data, file)
|
||||
return redirect('/nostr')
|
||||
|
||||
response = make_response(redirect('/'))
|
||||
response.set_cookie('auth', '', expires=0)
|
||||
return response
|
||||
|
||||
|
||||
@app.route('/.well-known/wallets/<path:path>')
|
||||
def wallets(path):
|
||||
# Check if host is in domains
|
||||
@ -332,6 +436,43 @@ def wallets(path):
|
||||
response.headers['Content-Type'] = 'text/plain'
|
||||
return response
|
||||
return render_template('404.html', year=datetime.datetime.now().year), 404
|
||||
|
||||
@app.route('/.well-known/nostr.json')
|
||||
def nostr_account():
|
||||
# Check if host is in domains
|
||||
if request.host in DOMAINS:
|
||||
# Check if user is logged in
|
||||
if 'auth' not in request.cookies:
|
||||
return redirect(f'https://{DOMAINS[0]}')
|
||||
auth = request.cookies['auth']
|
||||
for i in cookies:
|
||||
if i['cookie'] == auth:
|
||||
# Load site content
|
||||
if os.path.isfile(f'sites/{i["name"]}.json'):
|
||||
with open(f'sites/{i["name"]}.json') as file:
|
||||
data = json.load(file)
|
||||
if 'nostr' in data:
|
||||
nostr = data['nostr']
|
||||
# Return as plain text
|
||||
response = make_response(nostr_module.json(nostr))
|
||||
response.headers['Content-Type'] = 'text/plain'
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
# Get wallet from domain
|
||||
host = request.host.split(':')[0]
|
||||
|
||||
if os.path.isfile(f'sites/{host}.json'):
|
||||
with open(f'sites/{host}.json') as file:
|
||||
data = json.load(file)
|
||||
if 'nostr' in data:
|
||||
nostr = data['nostr']
|
||||
# Return as plain text
|
||||
response = make_response(nostr_module.json(nostr))
|
||||
response.headers['Content-Type'] = 'text/plain'
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
return render_template('404.html', year=datetime.datetime.now().year), 404
|
||||
|
||||
|
||||
@app.route('/publishing')
|
||||
@ -356,6 +497,38 @@ def auth():
|
||||
resp.set_cookie('auth', auth_cookie)
|
||||
return resp
|
||||
|
||||
@app.route('/auth', methods=['GET'])
|
||||
def auth_get():
|
||||
global cookies
|
||||
|
||||
if 'username' not in request.args:
|
||||
return redirect('/?error=Failed to login&reason=No username')
|
||||
if 'token' not in request.args:
|
||||
return redirect('/?error=Failed to login&reason=No token')
|
||||
|
||||
username = request.args['username']
|
||||
token = request.args['token']
|
||||
|
||||
# Check if user is valid
|
||||
r = requests.get(f'https://login.hns.au/auth/user?token={token}')
|
||||
r = r.json()
|
||||
|
||||
if 'error' in r:
|
||||
return redirect('/?error=Failed to login&reason=' + r['error'])
|
||||
|
||||
if r['username'] != username:
|
||||
return redirect('/?error=Failed to login&reason=Username mismatch')
|
||||
|
||||
auth_cookie = secrets.token_hex(12 // 2)
|
||||
cookies.append({'name': username, 'cookie': auth_cookie})
|
||||
|
||||
with open('cookies.json', 'w') as file:
|
||||
json.dump(cookies, file)
|
||||
|
||||
resp = make_response(redirect('/site'))
|
||||
resp.set_cookie('auth', auth_cookie)
|
||||
return resp
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
global cookies
|
||||
|
29
nostr.py
Normal file
29
nostr.py
Normal file
@ -0,0 +1,29 @@
|
||||
from base64 import b32decode
|
||||
from base64 import b16encode
|
||||
from bech32 import bech32_decode
|
||||
from bech32 import bech32_encode
|
||||
|
||||
B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
|
||||
|
||||
def add_padding(base32_len):
|
||||
bits = base32_len * 5
|
||||
padding_size = (8 - (bits % 8)) % 8
|
||||
return padding_size
|
||||
|
||||
def npub_to_hex(npub):
|
||||
hrd, data = bech32_decode(npub)
|
||||
b32_data = [B32[index] for index in data]
|
||||
data_str = "".join(b32_data)
|
||||
data_length = len(data_str)
|
||||
data_str += "=" * add_padding(data_length)
|
||||
decoded_data = b32decode(data_str)
|
||||
b16_encoded_data = b16encode(decoded_data)
|
||||
hex_str = b16_encoded_data.decode("utf-8").lower()
|
||||
return hex_str
|
||||
|
||||
def json(links):
|
||||
names = {}
|
||||
for link in links:
|
||||
names[link['name']] = npub_to_hex(link['pub'])
|
||||
|
||||
return {'names':names}
|
12
render.py
12
render.py
@ -11,6 +11,14 @@ def varo_login():
|
||||
}
|
||||
});'''
|
||||
|
||||
def hnslogin():
|
||||
# Redirect to https://login.hns.au/auth?return={{scheme}}{{host}}/auth
|
||||
|
||||
return """
|
||||
const { protocol, hostname, port } = window.location;
|
||||
const rootUrl = `${protocol}//${hostname}${port ? `:${port}` : ''}/auth`;
|
||||
window.location.href = `https://login.hns.au/auth?return=${encodeURIComponent(rootUrl)}`;
|
||||
"""
|
||||
|
||||
def preview(data):
|
||||
title = data['title']
|
||||
@ -63,7 +71,7 @@ def address_links(addresses,foreground):
|
||||
html = ''
|
||||
for address in addresses:
|
||||
token = address['token'].upper()
|
||||
html += f'<a href=".well-known/wallets/{token}" target="_blank">{tokenImage(token,foreground)}</a>'
|
||||
html += f'<a style="margin:5px;" href=".well-known/wallets/{token}" target="_blank">{tokenImage(token,foreground)}</a>'
|
||||
return html
|
||||
|
||||
def tokenImage(token,foreground):
|
||||
@ -91,7 +99,7 @@ def site(data, injectSSL = False):
|
||||
|
||||
ssl = ''
|
||||
if injectSSL:
|
||||
ssl = '<script src="https://nathan.woodburn/https.js" async=""></script>'
|
||||
ssl = f'<script src="https://{injectSSL}/https.js" async=""></script>'
|
||||
page = "page.html"
|
||||
if data['image'] == "":
|
||||
page = "page_no_image.html"
|
||||
|
@ -2,4 +2,5 @@ flask
|
||||
python-dotenv
|
||||
gunicorn
|
||||
requests
|
||||
apscheduler
|
||||
apscheduler
|
||||
bech32
|
@ -5,7 +5,15 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>HNS Links</title>
|
||||
<meta name="twitter:title" content="HNS Links">
|
||||
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="HNS Links">
|
||||
<meta name="description" content="Create a links page for your domain">
|
||||
<meta property="og:description" content="Create a links page for your domain">
|
||||
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:description" content="Create a links page for your domain">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
|
2
templates/assets/css/styles.min.css
vendored
2
templates/assets/css/styles.min.css
vendored
@ -1 +1 @@
|
||||
.video-container{position:relative;padding-bottom:56.25%;padding-top:0;height:0;overflow:hidden}.video-container embed,.video-container iframe,.video-container object{position:absolute;top:0;left:0;width:100%;height:100%}.social-icons{color:#313437;background-color:#fff;padding:70px 0}@media (max-width:767px){.social-icons{padding:50px 0}}.social-div{display:flex;justify-content:center;align-items:center}.social-list{max-width:100%;display:flex;list-style:none;gap:2.5rem}@media (max-width:500px){.social-list{gap:1.5rem}}@media (max-width:420px){.social-list{gap:0}}.custom-button{max-width:500px}.addresses{margin-bottom:20px;display:flex;justify-content:center;align-items:center}.social-icons i{color:#757980;margin:0 10px;width:60px;height:60px;border:1px solid #c8ced7;text-align:center;border-radius:50%;line-height:60px;display:inline-block}.social-link a{text-decoration:none;width:4.8rem;height:4.8rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a::before{content:"";position:absolute;width:100%;height:100%;background:#000;z-index:0;scale:1 0;transform-origin:bottom;transition:scale .5s}.social-link:hover a::before{scale:1 1}.icon{font-size:2rem;color:#011827;transition:.5s;z-index:2}.social-link a:hover .icon{color:#fff;transform:rotateY(360deg)}
|
||||
.video-container{position:relative;padding-bottom:56.25%;padding-top:0;height:0;overflow:hidden}.video-container embed,.video-container iframe,.video-container object{position:absolute;top:0;left:0;width:100%;height:100%}.nostr{margin-top:1em}.social-icons{color:#313437;background-color:#fff;padding:70px 0}@media (max-width:767px){.social-icons{padding:50px 0}}.social-div{display:flex;justify-content:center;align-items:center}.social-list{max-width:100%;display:flex;list-style:none;gap:2.5rem}@media (max-width:500px){.social-list{gap:1.5rem}}@media (max-width:420px){.social-list{gap:0}}.custom-button{max-width:500px}.addresses{margin-bottom:20px;display:flex;justify-content:center;align-items:center}.social-icons i{color:#757980;margin:0 10px;width:60px;height:60px;border:1px solid #c8ced7;text-align:center;border-radius:50%;line-height:60px;display:inline-block}.social-link a{text-decoration:none;width:4.8rem;height:4.8rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a::before{content:"";position:absolute;width:100%;height:100%;background:#000;z-index:0;scale:1 0;transform-origin:bottom;transition:scale .5s}.social-link:hover a::before{scale:1 1}.icon{font-size:2rem;color:#011827;transition:.5s;z-index:2}.social-link a:hover .icon{color:#fff;transform:rotateY(360deg)}
|
6
templates/https.js
Normal file
6
templates/https.js
Normal file
@ -0,0 +1,6 @@
|
||||
if (location.protocol !== 'https:') {
|
||||
location.replace(`https:${location.href.substring(location.protocol.length)}`);
|
||||
}
|
||||
else{
|
||||
console.log("Already Https");
|
||||
}
|
@ -5,7 +5,23 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>HNS Links</title>
|
||||
<meta name="twitter:title" content="HNS Links">
|
||||
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="HNS Links">
|
||||
<meta name="description" content="Create a links page for your domain">
|
||||
<meta property="og:description" content="Create a links page for your domain">
|
||||
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:description" content="Create a links page for your domain">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "HNS Links",
|
||||
"url": "https://links.hns.au"
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
|
111
templates/nostr.html
Normal file
111
templates/nostr.html
Normal file
@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Nostr - HNS Links</title>
|
||||
<meta name="twitter:title" content="HNS Links">
|
||||
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="HNS Links">
|
||||
<meta name="description" content="Create a links page for your domain">
|
||||
<meta property="og:description" content="Create a links page for your domain">
|
||||
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:description" content="Create a links page for your domain">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
||||
<script async src="https://umami.woodburn.au/script.js" data-website-id="a165bb1c-eef8-4737-8066-fcd8b4a380bd"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
|
||||
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/favicon.png" width="45px"><span> HNS Links</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
|
||||
<div class="collapse navbar-collapse" id="navcol-1">
|
||||
<ul class="navbar-nav mx-auto"></ul><a class="btn btn-primary shadow" role="button" href="/preview" style="margin-right: 10px;">Preview</a><a class="btn btn-primary shadow" role="button" href="/logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="bg-dark">
|
||||
<div class="container pt-4 pt-xl-5">
|
||||
<div class="row pt-5">
|
||||
<div class="col-md-8 col-xl-6 text-center text-md-start mx-auto">
|
||||
<div class="text-center">
|
||||
<p class="fw-bold text-success mb-2">{{domain}}/</p>
|
||||
<h1 class="fw-bold">Manage your site</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section>
|
||||
<div style="padding: 1em;padding-bottom: 25px;max-width: 800px;margin: auto;">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<h1>Nostr</h1>
|
||||
<div style="margin-bottom: 25px;">
|
||||
<h4>Nostr Links</h4>{% for link in nostr %}
|
||||
<div class='nostr'>
|
||||
<input class="form-control" type="text" style="display: inline-block;width: auto;" value="{{link.name}}" readonly/>
|
||||
<input class="form-control" type="text" style="display: inline-block;width: auto;" value="{{link.pub}}" readonly/>
|
||||
<a class="btn btn-primary" role="button" href="/nostr/delete/{{link.id}}"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"></path>
|
||||
</svg></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class='nostr'>
|
||||
<span>Link a new nostr profile</span><br>
|
||||
<input class="form-control" type="text" style="display: inline-block;width: auto;" name="new-name" placeholder="User Name" />
|
||||
<input class="form-control" type="text" style="display: inline-block;width: auto;" name="new-pub" placeholder="npub..." />
|
||||
<br><br>
|
||||
<input class="btn btn-primary" type="submit" value="Link" style="margin-right: 20px;" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<section></section>
|
||||
<footer class="bg-dark">
|
||||
<div class="container py-4 py-lg-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">Services</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://hnshosting.au" target="_blank">Wordpress Hosting</a></li>
|
||||
<li><a href="https://firewallet.au" target="_blank">FireWallet</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
|
||||
<h3 class="fs-6 fw-bold">About</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="https://hns.au" target="_blank">HNSAU</a></li>
|
||||
<li><a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
|
||||
<div class="fw-bold d-flex align-items-center mb-2"><span>HNS Links</span></div>
|
||||
<p class="text-muted">Easy to manage links page on Handshake domains</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
|
||||
<p class="mb-0">Copyright © {{year}} Nathan.Woodburn/</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/assets/js/jquery.min.js"></script>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="https://auth.varo.domains/v1"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -5,6 +5,10 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>{{title}}</title>
|
||||
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:title" content="{{title}}">
|
||||
<meta property="og:title" content="{{title}}">
|
||||
<meta name="description" content="{{title}}">
|
||||
|
@ -5,6 +5,10 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>{{title}}</title>
|
||||
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:title" content="{{title}}">
|
||||
<meta property="og:title" content="{{title}}">
|
||||
<meta name="description" content="{{title}}">
|
||||
|
@ -5,7 +5,15 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>HNS Links</title>
|
||||
<meta name="twitter:title" content="HNS Links">
|
||||
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="HNS Links">
|
||||
<meta name="description" content="Create a links page for your domain">
|
||||
<meta property="og:description" content="Create a links page for your domain">
|
||||
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:description" content="Create a links page for your domain">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
|
@ -5,7 +5,15 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Home - HNS Links</title>
|
||||
<meta name="twitter:title" content="HNS Links">
|
||||
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="HNS Links">
|
||||
<meta name="description" content="Create a links page for your domain">
|
||||
<meta property="og:description" content="Create a links page for your domain">
|
||||
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
|
||||
<meta name="twitter:description" content="Create a links page for your domain">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
|
||||
@ -88,7 +96,7 @@
|
||||
</div><input class="btn btn-primary" type="submit" value="Save" style="margin-right: 20px;"><a class="btn btn-primary" role="button" href="/publish">Publish</a>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6" style="text-align: center;">{{preview|safe}}</div>
|
||||
<div class="col-md-6" style="text-align: center;">{{preview|safe}}<a class="btn btn-primary" role="button" href="/nostr" style="margin-top: 1em;">Link Nostr</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user