unified add form, remove page view endpoint, drop mesh nav/section
This commit is contained in:
parent
d0b27b4c61
commit
cc71d9c577
6 changed files with 117 additions and 129 deletions
|
|
@ -18,7 +18,6 @@ from ._helpers import (
|
|||
from .search import handle_search
|
||||
from .pages import (
|
||||
handle_add_form, handle_add_submit, handle_add_manual_submit,
|
||||
handle_page_view,
|
||||
handle_pages, _render_bulk_delete_confirm, handle_bulk_action,
|
||||
handle_edit_form, handle_edit_submit,
|
||||
handle_delete_confirm, handle_delete,
|
||||
|
|
@ -66,9 +65,6 @@ def _dispatch_inner(data):
|
|||
elif path == "/add":
|
||||
prefill_url = query.get("url", [""])[0].strip()
|
||||
return handle_add_form(prefill_url=prefill_url)
|
||||
elif path.startswith("/pages/"):
|
||||
pid = extract_id("/pages/")
|
||||
return handle_page_view(pid) if pid is not None else _error(400)
|
||||
elif path == "/pages":
|
||||
return handle_pages(query)
|
||||
elif path.startswith("/edit/"):
|
||||
|
|
|
|||
|
|
@ -29,55 +29,50 @@ def handle_add_form(msg="", action_type="index", prefill_url=""):
|
|||
)
|
||||
url_value = f'value="{esc(prefill_url)}" ' if prefill_url else ""
|
||||
return _respond(
|
||||
f"<h1>add url</h1>"
|
||||
f"<p>Add a site to your index</p>"
|
||||
f"<h1>add site</h1>"
|
||||
f"<p>Add a site to your index — URL or RNS destination hash</p>"
|
||||
f'<form method="post" action="/add">'
|
||||
f'{_csrf_field()}'
|
||||
f'<input type="hidden" name="input_type" value="url">'
|
||||
f'<input name="url" placeholder="https://example.com" size="50" {url_value}><br><br>'
|
||||
f'<input name="url" placeholder="https://example.com or 32-char RNS hash" size="50" {url_value}><br><br>'
|
||||
f'<input name="note" placeholder="why are you saving this? (optional)" size="50"><br><br>'
|
||||
f'<input name="tags" placeholder="tags (comma-separated, e.g. solarpunk, mesh)" size="50"><br>'
|
||||
f'<small>tag: private to exclude from sharing</small><br><br>'
|
||||
f'<button type="submit">index</button>'
|
||||
f"</form>"
|
||||
f"<br><hr><br>"
|
||||
f"<h2>add mesh site</h2>"
|
||||
f"<p>Browse a remote TinyWeb instance over Reticulum</p>"
|
||||
f'<form method="post" action="/add">'
|
||||
f'{_csrf_field()}'
|
||||
f'<input type="hidden" name="input_type" value="rns">'
|
||||
f'<input name="reticulum_dest" placeholder="RNS destination hash (32 hex chars)" size="50" style="font-family:monospace"><br><br>'
|
||||
f'<input name="name" placeholder="name (optional)" size="50"><br><br>'
|
||||
f'<button type="submit">add mesh site</button>'
|
||||
f"</form>"
|
||||
f"<p>{msg}</p>"
|
||||
f'<a href="/">back</a>'
|
||||
)
|
||||
|
||||
|
||||
def handle_add_submit(body):
|
||||
input_type = body.get("input_type", ["url"])[0]
|
||||
url = body.get("url", [""])[0].strip()
|
||||
reticulum_dest = body.get("reticulum_dest", [""])[0].strip().replace("<", "").replace(">", "")
|
||||
name = body.get("name", [""])[0].strip()
|
||||
raw = body.get("url", [""])[0].strip().replace("<", "").replace(">", "")
|
||||
note = body.get("note", [""])[0].strip()
|
||||
tags = body.get("tags", [""])[0].strip()
|
||||
|
||||
if input_type == "rns":
|
||||
if not reticulum_dest:
|
||||
return handle_add_form("RNS destination hash is required.")
|
||||
if len(reticulum_dest) != 32 or not all(c in "0123456789abcdefABCDEF" for c in reticulum_dest):
|
||||
return handle_add_form("Invalid RNS destination hash. Must be 32 hex characters.")
|
||||
if not raw:
|
||||
return handle_add_form("URL or RNS hash is required.")
|
||||
|
||||
is_rns = (
|
||||
len(raw) == 32
|
||||
and all(c in "0123456789abcdefABCDEF" for c in raw)
|
||||
)
|
||||
if raw.startswith("rns:") or raw.startswith("RNS:"):
|
||||
raw = raw[4:]
|
||||
is_rns = (
|
||||
len(raw) == 32
|
||||
and all(c in "0123456789abcdefABCDEF" for c in raw)
|
||||
)
|
||||
|
||||
if is_rns:
|
||||
from .rns import handle_rns_add_hash
|
||||
handle_rns_add_hash(reticulum_dest, name)
|
||||
errs = handle_rns_add_hash(raw)
|
||||
if errs:
|
||||
return handle_add_form(f"Hash saved but indexing failed: {'; '.join(errs)}")
|
||||
return _redirect("/")
|
||||
|
||||
if input_type == "url":
|
||||
if not url:
|
||||
return handle_add_form("URL is required.")
|
||||
url = clean_url(url)
|
||||
url = clean_url(raw)
|
||||
if not url.startswith(("http://", "https://")):
|
||||
return handle_add_form("URL must start with http:// or https://")
|
||||
return handle_add_form("Enter a URL (http:// or https://) or a 32-char RNS destination hash.")
|
||||
|
||||
try:
|
||||
title = index_url(url, note)
|
||||
|
|
@ -114,8 +109,6 @@ def handle_add_submit(body):
|
|||
f'<a href="/">back</a>'
|
||||
)
|
||||
return handle_add_form(f"Error: could not fetch or index that URL. {esc(str(e)[:100])}")
|
||||
else:
|
||||
return handle_add_form("Invalid input type.")
|
||||
|
||||
|
||||
def handle_add_manual_submit(body):
|
||||
|
|
@ -162,32 +155,6 @@ def handle_add_manual_submit(body):
|
|||
return_db(db)
|
||||
|
||||
|
||||
def handle_page_view(page_id):
|
||||
db = get_db()
|
||||
try:
|
||||
row = db.execute("SELECT id, url, title, body, note FROM pages WHERE id = ?", (page_id,)).fetchone()
|
||||
if not row:
|
||||
return _error(404)
|
||||
tags = _get_page_tags(row["id"], db)
|
||||
tag_links = " ".join(f'<a href="/tags/{esc(t)}">[{esc(t)}]</a>' for t in tags) if tags else ""
|
||||
note_html = f'<p><em>{esc(row["note"])}</em></p>' if row["note"] else ""
|
||||
title = esc(row["title"] or "(untitled)")
|
||||
body_html = row["body"] or "(no content)"
|
||||
url_link = f'<p><small>source: <a href="{esc(row["url"])}" rel="noreferrer noopener">{esc(row["url"])}</a></small></p>'
|
||||
return _respond(
|
||||
f"<h1>{title}</h1>"
|
||||
f"{tag_links}"
|
||||
f"{note_html}"
|
||||
f"<hr>"
|
||||
f"{body_html}"
|
||||
f"<hr>"
|
||||
f"{url_link}"
|
||||
f'<a href="/pages">back to browse</a>'
|
||||
)
|
||||
finally:
|
||||
return_db(db)
|
||||
|
||||
|
||||
def handle_pages(query=None):
|
||||
msg = query.get("msg", [""])[0] if query else ""
|
||||
msg_html = f'<p class="success">{esc(msg)}</p>' if msg else ""
|
||||
|
|
@ -208,10 +175,17 @@ def handle_pages(query=None):
|
|||
if tags:
|
||||
tag_links = " ".join(f'<a href="/tags/{esc(t)}">[{esc(t)}]</a>' for t in tags)
|
||||
tags_html = f' {tag_links}'
|
||||
url = r["url"]
|
||||
if url.startswith("rns:"):
|
||||
display_url = url
|
||||
link_url = f"/rns/{esc(url[4:])}/"
|
||||
else:
|
||||
display_url = url
|
||||
link_url = url
|
||||
items += (
|
||||
f'<li><label><input type="checkbox" name="ids" value="{r["id"]}"> '
|
||||
f'<a href="/pages/{r["id"]}">{esc(r["title"])}</a></label>{note_html}{tags_html} '
|
||||
f'<small>(<a href="{esc(r["url"])}" rel="noreferrer noopener">{esc(r["url"])}</a>)</small> '
|
||||
f'{esc(r["title"])}</label>{note_html}{tags_html} '
|
||||
f'<small>(<a href="{esc(link_url)}" rel="noreferrer noopener">{esc(display_url)}</a>)</small> '
|
||||
f'<a href="/edit/{r["id"]}?p={page}">edit</a> '
|
||||
f'<a href="/delete/{r["id"]}">remove</a></li>'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import traceback
|
||||
from tinyweb.db import get_db, return_db
|
||||
from tinyweb.rns_client import fetch_remote_page
|
||||
from tinyweb.templates import esc
|
||||
|
|
@ -14,15 +15,49 @@ def _get_mesh_sites():
|
|||
|
||||
def handle_rns_add_hash(dest_hash, name=""):
|
||||
db = get_db()
|
||||
errors = []
|
||||
try:
|
||||
db.execute(
|
||||
"INSERT OR REPLACE INTO mesh_sites (hash, name) VALUES (?, ?)",
|
||||
(dest_hash, name or ""),
|
||||
)
|
||||
|
||||
try:
|
||||
resp = fetch_remote_page(dest_hash, "/")
|
||||
if resp.get("status") == 200:
|
||||
body = resp.get("body", "")
|
||||
title = name or dest_hash[:16]
|
||||
import re
|
||||
m = re.search(r"<title[^>]*>(.*?)</title>", body, re.IGNORECASE | re.DOTALL)
|
||||
if m:
|
||||
title = m.group(1).strip()
|
||||
desc = ""
|
||||
m = re.search(r'<meta\s+name="description"\s+content="([^"]*)"', body, re.IGNORECASE)
|
||||
if m:
|
||||
desc = m.group(1).strip()
|
||||
if not desc:
|
||||
text = re.sub(r"<[^>]+>", " ", body)
|
||||
text = re.sub(r"\s+", " ", text).strip()
|
||||
desc = text[:200].strip()
|
||||
url = f"rns:{dest_hash}"
|
||||
import datetime
|
||||
now = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||
db.execute(
|
||||
"INSERT OR REPLACE INTO pages (url, title, body, last_modified, summary) VALUES (?, ?, ?, ?, ?)",
|
||||
(url, title, body, now, desc),
|
||||
)
|
||||
else:
|
||||
errors.append(f"Remote returned status {resp.get('status')}")
|
||||
except Exception as e:
|
||||
errors.append(str(e))
|
||||
traceback.print_exc()
|
||||
|
||||
db.commit()
|
||||
finally:
|
||||
return_db(db)
|
||||
|
||||
return errors if errors else None
|
||||
|
||||
|
||||
def handle_rns_delete_hash(body):
|
||||
dest_hash = body.get("hash", [""])[0].strip() if isinstance(body, dict) else body
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from tinyweb.db import get_db, return_db, get_setting, get_site_name, clean_url
|
||||
from tinyweb.templates import esc
|
||||
from ._helpers import _sanitize_fts_query, _paginate, _get_page_tags, _respond, _page_nav, _csrf_field, PER_PAGE
|
||||
from ._helpers import _sanitize_fts_query, _paginate, _get_page_tags, _respond, _page_nav, PER_PAGE
|
||||
|
||||
|
||||
def handle_search(query):
|
||||
|
|
@ -83,10 +83,17 @@ def handle_search(query):
|
|||
tag_links = " ".join(f'<a href="/tags/{esc(t)}" class="tag">[{esc(t)}]</a>' for t in tags)
|
||||
tags_html = f'<div class="tags">{tag_links}</div>'
|
||||
snip_html = f'<br>{esc(r["summary"])}' if r["summary"] else ""
|
||||
url = r["url"]
|
||||
if url.startswith("rns:"):
|
||||
display_url = url
|
||||
link_url = f"/rns/{esc(url[4:])}/"
|
||||
else:
|
||||
display_url = url
|
||||
link_url = url
|
||||
result_html += (
|
||||
f'<div class="result">'
|
||||
f'<a href="{esc(r["url"])}" rel="noreferrer noopener">{esc(r["title"])}</a><br>'
|
||||
f'<small>{esc(r["url"])}</small>'
|
||||
f'<a href="{esc(link_url)}" rel="noreferrer noopener">{esc(r["title"])}</a><br>'
|
||||
f'<small>{esc(display_url)}</small>'
|
||||
f'{snip_html}'
|
||||
f'{note_html}{tags_html}'
|
||||
f'</div>'
|
||||
|
|
@ -163,30 +170,6 @@ def handle_search(query):
|
|||
if q and remote_rows:
|
||||
sub_count = f" + {len(remote_rows)} from subscriptions"
|
||||
|
||||
mesh_html = ""
|
||||
if not q:
|
||||
from .rns import _get_mesh_sites
|
||||
sites = _get_mesh_sites()
|
||||
if sites:
|
||||
rows_html = ""
|
||||
for s in sites:
|
||||
rows_html += (
|
||||
f'<tr>'
|
||||
f'<td><a href="/rns/{esc(s["hash"])}/">{esc(s["name"] or s["hash"][:16])}</a></td>'
|
||||
f'<td style="font-family:monospace;font-size:0.85rem">{esc(s["hash"])}</td>'
|
||||
f'<td><form action="/rns/delete" method="POST" style="display:inline">'
|
||||
f'{_csrf_field()}'
|
||||
f'<input type="hidden" name="hash" value="{esc(s["hash"])}">'
|
||||
f'<button type="submit">delete</button>'
|
||||
f'</form></td>'
|
||||
f'</tr>'
|
||||
)
|
||||
mesh_html = (
|
||||
f"<br>"
|
||||
f"<h2>mesh sites</h2>"
|
||||
f'<table>{rows_html}</table>'
|
||||
)
|
||||
|
||||
welcome_html = ""
|
||||
if count == 0 and not q:
|
||||
welcome_html = (
|
||||
|
|
@ -209,5 +192,5 @@ def handle_search(query):
|
|||
f'{welcome_html}'
|
||||
f'{result_html}'
|
||||
f'{_page_nav(page, total_results, f"/?q={esc(q)}") if q else ""}'
|
||||
f'{trusted_html}{remote_html}{mesh_html}'
|
||||
f'{trusted_html}{remote_html}'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ def handle_tag_browse(tag_name, query=None):
|
|||
tags = _get_page_tags(r["id"], db)
|
||||
tag_links = " ".join(f'<a href="/tags/{esc(t)}">[{esc(t)}]</a>' for t in tags)
|
||||
items += (
|
||||
f'<li><a href="/pages/{r["id"]}">{esc(r["title"])}</a>{note_html} {tag_links} '
|
||||
f'<li>{esc(r["title"])}{note_html} {tag_links} '
|
||||
f'<small>(<a href="{esc(r["url"])}" rel="noreferrer noopener">{esc(r["url"])}</a>)</small></li>'
|
||||
)
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ def _nav_html():
|
|||
return (
|
||||
f'<p><b><a href="/">{name}</a></b>'
|
||||
' | <a href="/">search</a> | <a href="/pages">browse</a>'
|
||||
' | <a href="/tags">tags</a> | <a href="/subscriptions">subscriptions</a> | <a href="/rns">mesh</a>'
|
||||
' | <a href="/tags">tags</a> | <a href="/subscriptions">subscriptions</a>'
|
||||
f'{forum_link}'
|
||||
' | <a href="/style">customize</a> | <a href="/about">about</a></p>\n'
|
||||
"<hr>\n"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue