DeckBro implementation
This commit is contained in:
@@ -303,6 +303,26 @@ Supported chat commands:
|
|||||||
- `/edcode`: extract code blocks from the most recent message and open `app.ed.edbuf()` on the contents
|
- `/edcode`: extract code blocks from the most recent message and open `app.ed.edbuf()` on the contents
|
||||||
- `/exit` or `/quit`: exit the chat applet
|
- `/exit` or `/quit`: exit the chat applet
|
||||||
|
|
||||||
|
### `app.bro`
|
||||||
|
|
||||||
|
An implementation of DeckBro, a simple text-only HTTP browser for [DeckText](decktext-spec.md) and simple HTML documents.
|
||||||
|
|
||||||
|
Usage: `from app.bro import bro`, then `bro(starting_url)`.
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
|
||||||
|
r)ef b)ack f)wd n)ext p)rev l)ink or pg#: q
|
||||||
|
|
||||||
|
- `g [url]`: go to a new URL
|
||||||
|
- `r`: refresh the current document
|
||||||
|
- `b`: go back in history
|
||||||
|
- `f`: go forward in history
|
||||||
|
- `n`: go to the next DeckText page of the document (this is the default action if you just press Enter)
|
||||||
|
- `p`: go to the previous DeckText page of the document
|
||||||
|
- `[num]`: jump to the page `[num]` if it exists
|
||||||
|
- `l [num]`: visit the link number `[num]` on the current page (if it exists)
|
||||||
|
- `q`: quit the browser
|
||||||
|
|
||||||
### `app.blog`
|
### `app.blog`
|
||||||
|
|
||||||
A simple interface to any POST-based blog API that supports HTTP Basic Auth.
|
A simple interface to any POST-based blog API that supports HTTP Basic Auth.
|
||||||
|
|||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
# DeckBro: a simple DeckText/HTML browser for T-DeckARD environment
|
||||||
|
# Usage: from app.bro import DeckBro
|
||||||
|
# Created by Luxferre in 2025-2026, released into public domain
|
||||||
|
|
||||||
|
from deck.pager import print_paged
|
||||||
|
from deck.input import input, input_multi
|
||||||
|
from deck.menu import menu
|
||||||
|
from deck.http import http_fetch, http_set_ua
|
||||||
|
from deck.text import DeckText
|
||||||
|
|
||||||
|
class DeckBro:
|
||||||
|
def __init__(self, start_url=None):
|
||||||
|
self.history = []
|
||||||
|
self.rendered_pages = []
|
||||||
|
self.history_pos = 0 # always refers to the current slot
|
||||||
|
self.url = start_url
|
||||||
|
self.doc = DeckText()
|
||||||
|
if self.url:
|
||||||
|
self.go(self.url)
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
pagenum = 1
|
||||||
|
pagetotal = len(self.doc.pages)
|
||||||
|
self.rendered_pages = []
|
||||||
|
for page in self.doc.pages:
|
||||||
|
outtext = f'{self.doc.title} ({pagenum}/{pagetotal})\n---\n'
|
||||||
|
outtext += page['content']
|
||||||
|
if len(page['links']) > 0:
|
||||||
|
linknum = 1
|
||||||
|
outtext += '\n--- Links ---\n'
|
||||||
|
for l in page['links']:
|
||||||
|
outtext += f'[{linknum}] {l[1]}: {l[0]}\n'
|
||||||
|
linknum += 1
|
||||||
|
self.rendered_pages.append(outtext)
|
||||||
|
pagenum += 1
|
||||||
|
return len(self.rendered_pages)
|
||||||
|
|
||||||
|
def display(self):
|
||||||
|
curpage = 1
|
||||||
|
totalpages = len(self.rendered_pages)
|
||||||
|
menustring = 'g)o r)ef b)ack f)wd n)ext p)rev l)ink or pg#: '
|
||||||
|
do_new_display = True
|
||||||
|
while True:
|
||||||
|
if do_new_display and curpage > 0 and curpage <= totalpages:
|
||||||
|
print_paged(self.rendered_pages[curpage - 1])
|
||||||
|
print(f'[{self.url}]')
|
||||||
|
user_choice = input(menustring).strip()
|
||||||
|
pgnum = None
|
||||||
|
try: # page number input detection
|
||||||
|
pgnum = int(user_choice)
|
||||||
|
except: pass
|
||||||
|
if pgnum is not None:
|
||||||
|
if pgnum > 0 and pgnum <= totalpages:
|
||||||
|
curpage = pgnum
|
||||||
|
do_new_display = True
|
||||||
|
else:
|
||||||
|
print('Invalid page number!')
|
||||||
|
do_new_display = False
|
||||||
|
continue
|
||||||
|
user_opt = ''
|
||||||
|
try:
|
||||||
|
user_opt = ' '.join(user_choice.split(' ')[1:])
|
||||||
|
except: pass
|
||||||
|
if user_choice == '':
|
||||||
|
user_choice = 'n' # go to the next page by default
|
||||||
|
ch_letter = user_choice[0].lower()
|
||||||
|
if ch_letter == 'q': break
|
||||||
|
elif ch_letter == 'n':
|
||||||
|
do_new_display = True
|
||||||
|
curpage += 1
|
||||||
|
if curpage > totalpages: # wrap around
|
||||||
|
curpage = 1
|
||||||
|
elif ch_letter == 'p':
|
||||||
|
do_new_display = True
|
||||||
|
curpage -= 1
|
||||||
|
if curpage < 1: # wrap around
|
||||||
|
curpage = totalpages
|
||||||
|
elif ch_letter == 'g':
|
||||||
|
self.go(user_opt)
|
||||||
|
break
|
||||||
|
elif ch_letter == 'l':
|
||||||
|
addr = None
|
||||||
|
try:
|
||||||
|
lnum = int(user_opt)
|
||||||
|
addr = self.doc.pages[curpage - 1]['links'][lnum - 1][0]
|
||||||
|
except: pass
|
||||||
|
if addr is None:
|
||||||
|
print('Invalid link number entered!')
|
||||||
|
do_new_display = False
|
||||||
|
else:
|
||||||
|
self.go(addr)
|
||||||
|
break
|
||||||
|
elif ch_letter == 'r':
|
||||||
|
self.refresh()
|
||||||
|
break
|
||||||
|
elif ch_letter == 'b':
|
||||||
|
if self.back() is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
do_new_display = False
|
||||||
|
elif ch_letter == 'f':
|
||||||
|
if self.forward() is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
do_new_display = False
|
||||||
|
|
||||||
|
def go(self, url=None, update_history=True):
|
||||||
|
if url:
|
||||||
|
self.url = url.strip()
|
||||||
|
status, pagetext = http_fetch(self.url)
|
||||||
|
if status != 200:
|
||||||
|
if pagetext == '':
|
||||||
|
pagetext = f'Error {status}'
|
||||||
|
else:
|
||||||
|
pagetext = f'Error {status}: \n{pagetext}'
|
||||||
|
self.doc.load(pagetext)
|
||||||
|
if len(self.doc.pages) > 0 and update_history:
|
||||||
|
self.history = self.history[0:self.history_pos+1]
|
||||||
|
self.history.append(self.url)
|
||||||
|
self.history_pos = len(self.history) - 1
|
||||||
|
self.render()
|
||||||
|
self.display()
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self.go(self.url, update_history=False)
|
||||||
|
|
||||||
|
def back(self):
|
||||||
|
if self.history_pos > 0:
|
||||||
|
self.history_pos -= 1
|
||||||
|
new_url = self.history[self.history_pos]
|
||||||
|
self.go(new_url, update_history=False)
|
||||||
|
return new_url
|
||||||
|
return None
|
||||||
|
|
||||||
|
def forward(self):
|
||||||
|
if self.history_pos < len(self.history) - 1:
|
||||||
|
self.history_pos += 1
|
||||||
|
new_url = self.history[self.history_pos]
|
||||||
|
self.go(new_url, update_history=False)
|
||||||
|
return new_url
|
||||||
|
return None
|
||||||
|
|
||||||
|
# create and start a default DeckBro instance
|
||||||
|
def bro(url=None):
|
||||||
|
inst = DeckBro()
|
||||||
|
inst.go(url)
|
||||||
@@ -15,6 +15,7 @@ from deck.http import auth_header, url_escape, form_encode
|
|||||||
from deck.otp import totp, hotp, get_epoch
|
from deck.otp import totp, hotp, get_epoch
|
||||||
from deck import xlat
|
from deck import xlat
|
||||||
from app.ed import edit, view, edbuf, viewbuf, edurl, viewurl
|
from app.ed import edit, view, edbuf, viewbuf, edurl, viewurl
|
||||||
|
from app.bro import bro
|
||||||
from app.blog import blogpost, blogpost_str, bloged
|
from app.blog import blogpost, blogpost_str, bloged
|
||||||
from app.llmchat import llmchat
|
from app.llmchat import llmchat
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user