Phase 5: merge test-reticulum-hash — radio toggle for URL vs hash, spacing fixes
This commit is contained in:
commit
b415f6265d
2 changed files with 72 additions and 21 deletions
17
db.py
17
db.py
|
|
@ -131,7 +131,8 @@ def init_db():
|
||||||
" title TEXT,"
|
" title TEXT,"
|
||||||
" body TEXT,"
|
" body TEXT,"
|
||||||
" note TEXT DEFAULT '',"
|
" note TEXT DEFAULT '',"
|
||||||
" last_modified TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%S','now'))"
|
" last_modified TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%S','now')),"
|
||||||
|
" reticulum_dest TEXT DEFAULT ''"
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
db.execute(
|
db.execute(
|
||||||
|
|
@ -247,6 +248,11 @@ def init_db():
|
||||||
db.execute("ALTER TABLE pages ADD COLUMN summary TEXT DEFAULT ''")
|
db.execute("ALTER TABLE pages ADD COLUMN summary TEXT DEFAULT ''")
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
# Migrate pages: add reticulum_dest column if missing
|
||||||
|
if "reticulum_dest" not in page_cols:
|
||||||
|
db.execute("ALTER TABLE pages ADD COLUMN reticulum_dest TEXT DEFAULT ''")
|
||||||
|
db.commit()
|
||||||
|
|
||||||
# Chunks table for semantic search embeddings
|
# Chunks table for semantic search embeddings
|
||||||
db.execute(
|
db.execute(
|
||||||
"CREATE TABLE IF NOT EXISTS chunks ("
|
"CREATE TABLE IF NOT EXISTS chunks ("
|
||||||
|
|
@ -359,19 +365,18 @@ def fetch_page(url):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def index_url(url, note=""):
|
def index_url(url, note="", reticulum_dest=""):
|
||||||
url = clean_url(url)
|
url = clean_url(url)
|
||||||
title, body, links, meta_desc = fetch_page(url)
|
title, body, links, meta_desc = fetch_page(url)
|
||||||
# Use meta description if available and meaningful, otherwise generate from body
|
|
||||||
summary = meta_desc if meta_desc and len(meta_desc) > 20 else ""
|
summary = meta_desc if meta_desc and len(meta_desc) > 20 else ""
|
||||||
db = get_db()
|
db = get_db()
|
||||||
try:
|
try:
|
||||||
now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) "
|
"INSERT INTO pages (url, title, body, note, last_modified, summary, reticulum_dest) VALUES (?, ?, ?, ?, ?, ?, ?) "
|
||||||
"ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, "
|
"ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, "
|
||||||
"note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary",
|
"note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary, reticulum_dest=excluded.reticulum_dest",
|
||||||
(url, title, body, note, now, summary),
|
(url, title, body, note, now, summary, reticulum_dest),
|
||||||
)
|
)
|
||||||
page_id = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone()[0]
|
page_id = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone()[0]
|
||||||
db.execute("DELETE FROM links WHERE page_id = ?", (page_id,))
|
db.execute("DELETE FROM links WHERE page_id = ?", (page_id,))
|
||||||
|
|
|
||||||
76
handlers.py
76
handlers.py
|
|
@ -338,9 +338,23 @@ def handle_search(query):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def handle_add_form(msg=""):
|
def handle_add_form(msg="", action_type="index"):
|
||||||
|
if action_type == "subscribe":
|
||||||
|
return _respond(
|
||||||
|
f"<h1>subscribe</h1>"
|
||||||
|
f"<p>Subscribe to a friend's TinyWeb instance to sync their index</p>"
|
||||||
|
f'<form method="post" action="/subscriptions/add">'
|
||||||
|
f'{_csrf_field()}'
|
||||||
|
f'<input name="dest_hash" placeholder="destination hash (32 hex chars)" size="50"><br><br>'
|
||||||
|
f'<button type="submit">subscribe</button>'
|
||||||
|
f"</form>"
|
||||||
|
f"<p><small>or <a href=\"/add\">add a single site</a></small></p>"
|
||||||
|
f"<p>{msg}</p>"
|
||||||
|
f'<a href="/">back</a>'
|
||||||
|
)
|
||||||
return _respond(
|
return _respond(
|
||||||
f"<h1>add url</h1>"
|
f"<h1>add url</h1>"
|
||||||
|
f"<p>Add a site to your index</p>"
|
||||||
f'<form method="post" action="/add">'
|
f'<form method="post" action="/add">'
|
||||||
f'{_csrf_field()}'
|
f'{_csrf_field()}'
|
||||||
f'<input name="url" placeholder="https://example.com" size="50"><br><br>'
|
f'<input name="url" placeholder="https://example.com" size="50"><br><br>'
|
||||||
|
|
@ -354,18 +368,27 @@ def handle_add_form(msg=""):
|
||||||
|
|
||||||
|
|
||||||
def handle_add_submit(body):
|
def handle_add_submit(body):
|
||||||
url = clean_url(body.get("url", [""])[0].strip())
|
input_type = body.get("input_type", ["url"])[0]
|
||||||
|
url = body.get("url", [""])[0].strip()
|
||||||
|
reticulum_dest = body.get("reticulum_dest", [""])[0].strip().replace("<", "").replace(">", "")
|
||||||
note = body.get("note", [""])[0].strip()
|
note = body.get("note", [""])[0].strip()
|
||||||
tags = body.get("tags", [""])[0].strip()
|
tags = body.get("tags", [""])[0].strip()
|
||||||
|
|
||||||
if not url:
|
if input_type == "url":
|
||||||
return handle_add_form("URL is required.")
|
if not url:
|
||||||
if not url.startswith(("http://", "https://")):
|
return handle_add_form("URL is required.")
|
||||||
return handle_add_form("URL must start with http:// or https://")
|
url = clean_url(url)
|
||||||
|
if not url.startswith(("http://", "https://")):
|
||||||
|
return handle_add_form("URL must start with http:// or https://")
|
||||||
|
else:
|
||||||
|
if not reticulum_dest:
|
||||||
|
return handle_add_form("Reticulum 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 reticulum destination hash. Must be 32 hex characters.")
|
||||||
|
url = f"reticulum:{reticulum_dest}"
|
||||||
|
|
||||||
# Try auto-index first
|
|
||||||
try:
|
try:
|
||||||
title = index_url(url, note)
|
title = index_url(url, note, reticulum_dest if reticulum_dest else "")
|
||||||
if tags:
|
if tags:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
try:
|
try:
|
||||||
|
|
@ -375,7 +398,8 @@ def handle_add_submit(body):
|
||||||
db.commit()
|
db.commit()
|
||||||
finally:
|
finally:
|
||||||
return_db(db)
|
return_db(db)
|
||||||
return handle_add_form(f'Indexed: <a href="{esc(url)}">{esc(title)}</a>')
|
|
||||||
|
return handle_add_form(f'Indexed: {esc(url)}')
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return handle_add_form(f"Error: {esc(str(e))}")
|
return handle_add_form(f"Error: {esc(str(e))}")
|
||||||
|
|
@ -414,6 +438,7 @@ def handle_add_manual_submit(body):
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
return handle_add_form("URL is required.")
|
return handle_add_form("URL is required.")
|
||||||
|
|
||||||
if not manual_title or not manual_desc:
|
if not manual_title or not manual_desc:
|
||||||
return handle_add_form("Title and description are required for manual entry.")
|
return handle_add_form("Title and description are required for manual entry.")
|
||||||
|
|
||||||
|
|
@ -421,7 +446,6 @@ def handle_add_manual_submit(body):
|
||||||
try:
|
try:
|
||||||
now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
# Insert the page
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) "
|
"INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) "
|
||||||
"ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, "
|
"ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, "
|
||||||
|
|
@ -455,6 +479,8 @@ def handle_add_manual_submit(body):
|
||||||
|
|
||||||
|
|
||||||
def handle_pages(query=None):
|
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 ""
|
||||||
page = _paginate(query or {})
|
page = _paginate(query or {})
|
||||||
offset = (page - 1) * BROWSE_PER_PAGE
|
offset = (page - 1) * BROWSE_PER_PAGE
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
@ -482,6 +508,7 @@ def handle_pages(query=None):
|
||||||
return_db(db)
|
return_db(db)
|
||||||
return _respond(
|
return _respond(
|
||||||
f"<h1>indexed pages ({total})</h1>"
|
f"<h1>indexed pages ({total})</h1>"
|
||||||
|
f"{msg_html}"
|
||||||
f"<ul>{items}</ul>"
|
f"<ul>{items}</ul>"
|
||||||
f'{_page_nav(page, total, "/pages", BROWSE_PER_PAGE)}'
|
f'{_page_nav(page, total, "/pages", BROWSE_PER_PAGE)}'
|
||||||
f'<p><a href="/export">export</a> | <a href="/import">import</a></p>'
|
f'<p><a href="/export">export</a> | <a href="/import">import</a></p>'
|
||||||
|
|
@ -492,20 +519,27 @@ def handle_pages(query=None):
|
||||||
def handle_edit_form(page_id, msg=""):
|
def handle_edit_form(page_id, msg=""):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
try:
|
try:
|
||||||
row = db.execute("SELECT id, url, title, note FROM pages WHERE id = ?", (page_id,)).fetchone()
|
row = db.execute("SELECT id, url, title, body, note, summary FROM pages WHERE id = ?", (page_id,)).fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
return _error(404)
|
return _error(404)
|
||||||
tags = ", ".join(_get_page_tags(page_id, db))
|
tags = ", ".join(_get_page_tags(page_id, db))
|
||||||
finally:
|
finally:
|
||||||
return_db(db)
|
return_db(db)
|
||||||
|
|
||||||
return _respond(
|
return _respond(
|
||||||
f"<h1>edit page</h1>"
|
f"<h1>edit page</h1>"
|
||||||
f"<p><b>{esc(row['title'])}</b><br>"
|
f"<p><b>{esc(row['title'])}</b><br>"
|
||||||
f"<small>{esc(row['url'])}</small></p>"
|
f"<small>{esc(row['url'])}</small></p>"
|
||||||
f'<form method="post" action="/edit/{row["id"]}">'
|
f'<form method="post" action="/edit/{row["id"]}">'
|
||||||
f'{_csrf_field()}'
|
f'{_csrf_field()}'
|
||||||
f'<input name="note" value="{esc(row["note"])}" placeholder="why did you save this?" size="50"><br><br>'
|
f'<label>Title:</label><br>'
|
||||||
f'<input name="tags" value="{esc(tags)}" placeholder="tags (comma-separated)" size="50"><br><br>'
|
f'<input name="title" value="{esc(row["title"])}" size="60"><br><br>'
|
||||||
|
f'<label>Summary (shown in search results):</label><br>'
|
||||||
|
f'<textarea name="summary" rows="3" cols="60">{esc(row["summary"] or "")}</textarea><br><br>'
|
||||||
|
f'<label>Note (why you saved this):</label><br>'
|
||||||
|
f'<input name="note" value="{esc(row["note"])}" size="50"><br><br>'
|
||||||
|
f'<label>Tags (comma-separated):</label><br>'
|
||||||
|
f'<input name="tags" value="{esc(tags)}" size="50"><br><br>'
|
||||||
f'<button type="submit">save</button>'
|
f'<button type="submit">save</button>'
|
||||||
f"</form>"
|
f"</form>"
|
||||||
f"<p>{msg}</p>"
|
f"<p>{msg}</p>"
|
||||||
|
|
@ -514,15 +548,25 @@ def handle_edit_form(page_id, msg=""):
|
||||||
|
|
||||||
|
|
||||||
def handle_edit_submit(page_id, body):
|
def handle_edit_submit(page_id, body):
|
||||||
|
title = body.get("title", [""])[0].strip()
|
||||||
|
summary = body.get("summary", [""])[0].strip()
|
||||||
note = body.get("note", [""])[0].strip()
|
note = body.get("note", [""])[0].strip()
|
||||||
tags = body.get("tags", [""])[0].strip()
|
tags = body.get("tags", [""])[0].strip()
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
try:
|
try:
|
||||||
db.execute("UPDATE pages SET note = ? WHERE id = ?", (note, page_id))
|
db.execute(
|
||||||
|
"UPDATE pages SET title = ?, summary = ?, note = ? WHERE id = ?",
|
||||||
|
(title, summary, note, page_id)
|
||||||
|
)
|
||||||
|
|
||||||
_set_page_tags(page_id, tags, db)
|
_set_page_tags(page_id, tags, db)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
return_db(db)
|
return_db(db)
|
||||||
|
|
||||||
return _redirect("/pages")
|
return _redirect("/pages")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -881,6 +925,7 @@ def handle_subscriptions(msg=""):
|
||||||
f'<input name="dest_hash" placeholder="destination hash" size="40"> '
|
f'<input name="dest_hash" placeholder="destination hash" size="40"> '
|
||||||
f'<button>subscribe</button>'
|
f'<button>subscribe</button>'
|
||||||
f'</form>'
|
f'</form>'
|
||||||
|
f'<p><small>or <a href="/add?type=subscribe">subscribe to an instance</a></small></p>'
|
||||||
f'<p>{msg}</p>'
|
f'<p>{msg}</p>'
|
||||||
f'<hr>{listing}'
|
f'<hr>{listing}'
|
||||||
f'<br><a href="/">back</a>'
|
f'<br><a href="/">back</a>'
|
||||||
|
|
@ -1246,7 +1291,8 @@ def _dispatch_inner(data):
|
||||||
if path == "/":
|
if path == "/":
|
||||||
return handle_search(query)
|
return handle_search(query)
|
||||||
elif path == "/add":
|
elif path == "/add":
|
||||||
return handle_add_form()
|
action_type = query.get("type", ["index"])[0]
|
||||||
|
return handle_add_form(action_type=action_type if action_type == "subscribe" else "index")
|
||||||
elif path == "/pages":
|
elif path == "/pages":
|
||||||
return handle_pages(query)
|
return handle_pages(query)
|
||||||
elif path.startswith("/edit/"):
|
elif path.startswith("/edit/"):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue