Compare commits
13 commits
0dd29c63ae
...
0b1a98fb73
Author | SHA1 | Date | |
---|---|---|---|
dece | 0b1a98fb73 | ||
dece | a468c94386 | ||
dece | d42a294516 | ||
dece | 4192c2a84d | ||
dece | 440f6357d8 | ||
dece | 690879f558 | ||
dece | 2d493af64b | ||
dece | f827ce3ee1 | ||
dece | 1468e6ef10 | ||
dece | 46ec9879e6 | ||
dece | b22981cef6 | ||
dece | 8e4f8c4c70 | ||
dece | 4738e495b2 |
14
BOARD.txt
14
BOARD.txt
|
@ -1,11 +1,8 @@
|
|||
TODO
|
||||
----------------------------------------
|
||||
dumb rendering mode per site
|
||||
well, preferences per site maybe?
|
||||
does encoding really work? cf. egsam
|
||||
add metadata to status bar
|
||||
more UT
|
||||
setup.py
|
||||
make client cert gen configurable
|
||||
|
||||
|
||||
|
||||
|
@ -22,12 +19,17 @@ buffers (tabs)
|
|||
a11y? tts?
|
||||
handle soft-hyphens on wrapping
|
||||
bug: combining chars reduce lengths
|
||||
non shit command-line
|
||||
use a pad for command-line
|
||||
use a pad for status bar
|
||||
response code 11 (if still there)
|
||||
gopher?
|
||||
opt. maintain history between sessions
|
||||
history (forward) (useful?)
|
||||
search in page (ugh)
|
||||
remember scroll pos in history
|
||||
identity management
|
||||
"previous/next" pages
|
||||
directory view for file scheme
|
||||
|
||||
|
||||
|
||||
|
@ -53,3 +55,5 @@ media files
|
|||
identity management
|
||||
logging
|
||||
home page
|
||||
different rendering mode
|
||||
preferences per site
|
||||
|
|
|
@ -17,9 +17,10 @@ from bebop.bookmarks import (
|
|||
save_bookmark,
|
||||
)
|
||||
from bebop.colors import ColorPair, init_colors
|
||||
from bebop.config import RENDER_MODES
|
||||
from bebop.command_line import CommandLine
|
||||
from bebop.external import open_external_program
|
||||
from bebop.fs import get_identities_list_path
|
||||
from bebop.fs import get_capsule_prefs_path, get_identities_list_path
|
||||
from bebop.help import get_help
|
||||
from bebop.history import History
|
||||
from bebop.identity import load_identities
|
||||
|
@ -36,6 +37,7 @@ from bebop.navigation import (
|
|||
)
|
||||
from bebop.page import Page
|
||||
from bebop.page_pad import PagePad
|
||||
from bebop.preferences import load_capsule_prefs, save_capsule_prefs
|
||||
from bebop.welcome import WELCOME_PAGE
|
||||
|
||||
|
||||
|
@ -77,7 +79,7 @@ class Browser:
|
|||
self.cache = {}
|
||||
self.special_pages = self.setup_special_pages()
|
||||
self.last_download: Optional[Tuple[MimeType, Path]] = None
|
||||
self.identities = load_identities(get_identities_list_path()) or {}
|
||||
self.identities = {}
|
||||
self._current_url = ""
|
||||
|
||||
@property
|
||||
|
@ -147,7 +149,25 @@ class Browser:
|
|||
self.config["command_editor"]
|
||||
)
|
||||
|
||||
if start_url:
|
||||
failed_to_load = []
|
||||
identities = load_identities(get_identities_list_path())
|
||||
if identities is None:
|
||||
failed_to_load.append("identities")
|
||||
else:
|
||||
self.identities = identities
|
||||
capsule_prefs = load_capsule_prefs(get_capsule_prefs_path())
|
||||
if capsule_prefs is None:
|
||||
failed_to_load.append("capsule preferences")
|
||||
else:
|
||||
self.capsule_prefs = capsule_prefs
|
||||
|
||||
if failed_to_load:
|
||||
error_msg = (
|
||||
f"Failed to open some local data: {', '.join(failed_to_load)}. "
|
||||
"Some data may be lost if you continue."
|
||||
)
|
||||
self.set_status_error(error_msg)
|
||||
elif start_url:
|
||||
self.open_url(start_url)
|
||||
else:
|
||||
self.open_home()
|
||||
|
@ -208,6 +228,8 @@ class Browser:
|
|||
self.edit_page()
|
||||
elif char == ord("y"):
|
||||
self.open_history()
|
||||
elif char == ord("§"):
|
||||
self.toggle_render_mode()
|
||||
elif curses.ascii.isdigit(char):
|
||||
self.handle_digit_input(char)
|
||||
elif char == curses.KEY_MOUSE:
|
||||
|
@ -229,15 +251,6 @@ class Browser:
|
|||
self.scroll_page_vertically(-1)
|
||||
elif char == ord("l"):
|
||||
self.scroll_page_horizontally(1)
|
||||
# elif char == ord("@"):
|
||||
# self.current_url = "bebop:debugzone"
|
||||
# t = "\n".join("* " + u for u in self.history.urls)
|
||||
# t += "\n\n" + "\n".join("* " + u for u in self.history.backlist)
|
||||
# self.load_page(Page.from_text(t))
|
||||
# # unctrled = curses.unctrl(char)
|
||||
# # if unctrled == b"^T":
|
||||
# # self.set_status("test!")
|
||||
# pass
|
||||
|
||||
@property
|
||||
def page_pad_size(self):
|
||||
|
@ -306,16 +319,22 @@ class Browser:
|
|||
|
||||
command = words[0]
|
||||
if num_words == 1:
|
||||
if command in ("q", "quit"):
|
||||
if command == "help":
|
||||
self.open_help()
|
||||
elif command in ("q", "quit"):
|
||||
self.running = False
|
||||
elif command in ("h", "home"):
|
||||
self.open_home()
|
||||
elif command in ("i", "info"):
|
||||
self.show_page_info()
|
||||
return
|
||||
if command in ("o", "open"):
|
||||
self.open_url(words[1])
|
||||
elif command == "forget-certificate":
|
||||
from bebop.browser.gemini import forget_certificate
|
||||
forget_certificate(self, words[1])
|
||||
elif command == "set-render-mode":
|
||||
self.set_render_mode(words[1])
|
||||
|
||||
def get_user_text_input(self, status_text, char, prefix="", strip=False):
|
||||
"""Get user input from the command-line."""
|
||||
|
@ -415,7 +434,7 @@ class Browser:
|
|||
self.set_status_error(f"Protocol '{scheme}' not supported.")
|
||||
|
||||
def load_page(self, page: Page):
|
||||
"""Load Gemtext data as the current page."""
|
||||
"""Set this page as the current page and refresh appropriate windows."""
|
||||
old_pad_height = self.page_pad.dim[0]
|
||||
self.page_pad.show_page(page)
|
||||
if self.page_pad.dim[0] < old_pad_height:
|
||||
|
@ -657,3 +676,52 @@ class Browser:
|
|||
def open_welcome_page(self):
|
||||
"""Open the default welcome page."""
|
||||
self.open_internal_page("welcome", WELCOME_PAGE)
|
||||
|
||||
def show_page_info(self):
|
||||
"""Show some page informations in the status bar."""
|
||||
if not self.page_pad or not self.page_pad.current_page:
|
||||
return
|
||||
page = self.page_pad.current_page
|
||||
mime = page.mime.short if page.mime else "(unknown MIME type)"
|
||||
encoding = page.encoding or "(unknown encoding)"
|
||||
size = f"{len(page.source)} chars"
|
||||
info = f"{mime} {encoding} {size}"
|
||||
self.set_status(info)
|
||||
|
||||
def set_render_mode(self, mode):
|
||||
"""Set the render mode for the current path or capsule."""
|
||||
if mode not in RENDER_MODES:
|
||||
valid_modes = ", ".join(RENDER_MODES)
|
||||
self.set_status_error("Valid render modes are: " + valid_modes)
|
||||
return
|
||||
url = self.get_user_text_input(
|
||||
f"Set '{mode}' render mode for which URL (includes children)?",
|
||||
CommandLine.CHAR_TEXT,
|
||||
prefix=self.current_url,
|
||||
strip=True
|
||||
)
|
||||
if not url:
|
||||
return
|
||||
prefs = self.capsule_prefs.get(url, {})
|
||||
prefs["render_mode"] = mode
|
||||
self.capsule_prefs[url] = prefs
|
||||
save_capsule_prefs(self.capsule_prefs, get_capsule_prefs_path())
|
||||
self.reload_page()
|
||||
|
||||
def toggle_render_mode(self):
|
||||
"""Switch to the next render mode for the current page."""
|
||||
if not self.page_pad or not self.page_pad.current_page:
|
||||
return
|
||||
page = self.page_pad.current_page
|
||||
if page.render is None or page.render not in RENDER_MODES:
|
||||
next_mode = RENDER_MODES[0]
|
||||
else:
|
||||
cur_mod_index = RENDER_MODES.index(page.render)
|
||||
next_mode = RENDER_MODES[(cur_mod_index + 1) % len(RENDER_MODES)]
|
||||
new_page = Page.from_gemtext(
|
||||
page.source,
|
||||
wrap_at=self.config["text_width"],
|
||||
render=next_mode
|
||||
)
|
||||
self.load_page(new_page)
|
||||
self.set_status(f"Using render mode '{next_mode}'.")
|
||||
|
|
|
@ -13,6 +13,7 @@ from bebop.identity import (
|
|||
)
|
||||
from bebop.navigation import set_parameter
|
||||
from bebop.page import Page
|
||||
from bebop.preferences import get_url_render_mode_pref
|
||||
from bebop.protocol import Request, Response
|
||||
from bebop.tofu import trust_fingerprint, untrust_fingerprint, WRONG_FP_ALERT
|
||||
|
||||
|
@ -73,7 +74,6 @@ def open_gemini_url(
|
|||
if use_cache and url in browser.cache:
|
||||
browser.load_page(browser.cache[url])
|
||||
browser.current_url = url
|
||||
browser.set_status(url)
|
||||
return url
|
||||
|
||||
logging.info(
|
||||
|
@ -214,10 +214,20 @@ def _handle_successful_response(browser: Browser, response: Response, url: str):
|
|||
except LookupError:
|
||||
error = f"Unknown encoding {encoding}."
|
||||
else:
|
||||
page = Page.from_gemtext(text, browser.config["text_width"])
|
||||
text_width = browser.config["text_width"]
|
||||
render_mode = get_url_render_mode_pref(
|
||||
browser.capsule_prefs,
|
||||
url,
|
||||
browser.config["render_mode"]
|
||||
)
|
||||
page = Page.from_gemtext(text, text_width, render=render_mode)
|
||||
else:
|
||||
text = response.content.decode("utf-8", errors="replace")
|
||||
encoding = "utf-8"
|
||||
text = response.content.decode(encoding, errors="replace")
|
||||
page = Page.from_text(text)
|
||||
if page:
|
||||
page.mime = mime_type
|
||||
page.encoding = encoding
|
||||
else:
|
||||
download_dir = browser.config["download_path"]
|
||||
filepath = _get_download_path(url, download_dir=download_dir)
|
||||
|
@ -228,7 +238,6 @@ def _handle_successful_response(browser: Browser, response: Response, url: str):
|
|||
browser.load_page(page)
|
||||
browser.current_url = url
|
||||
browser.cache[url] = page
|
||||
browser.set_status(url)
|
||||
return url
|
||||
elif filepath:
|
||||
try:
|
||||
|
|
|
@ -15,8 +15,11 @@ DEFAULT_CONFIG = {
|
|||
"external_commands": {},
|
||||
"external_command_default": ["xdg-open"],
|
||||
"home": "bebop:welcome",
|
||||
"render_mode": "fancy",
|
||||
}
|
||||
|
||||
RENDER_MODES = ("fancy", "dumb")
|
||||
|
||||
|
||||
def load_config(config_path):
|
||||
if not os.path.isfile(config_path):
|
||||
|
|
18
bebop/fs.py
18
bebop/fs.py
|
@ -39,12 +39,11 @@ def get_downloads_path() -> Path:
|
|||
if line.startswith("XDG_DOWNLOAD_DIR="):
|
||||
download_path = line.rstrip().split("=", maxsplit=1)[1]
|
||||
download_path = download_path.strip('"')
|
||||
download_path = download_path.replace("$HOME", expanduser("~"))
|
||||
break
|
||||
home = expanduser("~")
|
||||
download_path = download_path.replace("$HOME", home)
|
||||
return Path(download_path)
|
||||
except OSError:
|
||||
pass
|
||||
if download_path:
|
||||
return Path(download_path)
|
||||
return Path.home()
|
||||
|
||||
|
||||
|
@ -60,6 +59,12 @@ def get_identities_path():
|
|||
return get_user_data_path() / "identities"
|
||||
|
||||
|
||||
@lru_cache(None)
|
||||
def get_capsule_prefs_path():
|
||||
"""Return the directory where identities are stored."""
|
||||
return get_user_data_path() / "capsule_prefs.json"
|
||||
|
||||
|
||||
def ensure_bebop_files_exist() -> Optional[str]:
|
||||
"""Ensure various Bebop's files or directories are present.
|
||||
|
||||
|
@ -79,5 +84,10 @@ def ensure_bebop_files_exist() -> Optional[str]:
|
|||
identities_path = get_identities_path()
|
||||
if not identities_path.exists():
|
||||
identities_path.mkdir(parents=True)
|
||||
# Ensure the capsule preferences file exists.
|
||||
capsule_prefs_path = get_capsule_prefs_path()
|
||||
if not capsule_prefs_path.exists():
|
||||
with open(capsule_prefs_path, "wt") as prefs_file:
|
||||
prefs_file.write("{}")
|
||||
except OSError as exc:
|
||||
return str(exc)
|
||||
|
|
|
@ -54,7 +54,7 @@ class ListItem:
|
|||
ParsedGemtext = namedtuple("ParsedGemtext", ("elements", "links", "title"))
|
||||
|
||||
|
||||
def parse_gemtext(text: str) -> ParsedGemtext:
|
||||
def parse_gemtext(text: str, dumb=False) -> ParsedGemtext:
|
||||
"""Parse a string of Gemtext into a list of elements."""
|
||||
elements = []
|
||||
links = Links()
|
||||
|
@ -63,7 +63,9 @@ def parse_gemtext(text: str) -> ParsedGemtext:
|
|||
preformatted = None
|
||||
for line in text.splitlines():
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
# In standard mode, discard empty lines. In dumb mode, empty lines are
|
||||
# kept as basic text.
|
||||
if not line and not dumb:
|
||||
continue
|
||||
|
||||
if line.startswith(Preformatted.FENCE):
|
||||
|
|
|
@ -37,16 +37,20 @@ Keybinds using the SHIFT key are written uppercase. Keybinds using the ALT (or M
|
|||
* y: open history
|
||||
* digits: go to the corresponding link ID
|
||||
* escape: reset status line text
|
||||
* section sign (§): toggle between render modes for the current page
|
||||
* C-c: cancel current operation
|
||||
|
||||
## Commands
|
||||
|
||||
Commands are mostly for actions requiring user input. You can type a command with arguments by pressing the corresponding keybind above.
|
||||
|
||||
* o/open <url>: open this URL
|
||||
* q/quit: well, quit
|
||||
* h/home: open your home page
|
||||
* forget_certificate <hostname>: remove saved fingerprint for this hostname
|
||||
* help: show this help page
|
||||
* o(pen) <url>: open this URL
|
||||
* q(uit): well, quit
|
||||
* h(ome): open your home page
|
||||
* i(nfo): show page informations
|
||||
* forget-certificate <hostname>: remove saved fingerprint for this hostname
|
||||
* set-render-mode: set render mode preference for the current URL
|
||||
|
||||
## Configuration
|
||||
|
||||
|
@ -63,6 +67,7 @@ Here are the available options:
|
|||
* external_commands (see note 2): commands to open various files.
|
||||
* external_command_default (see note 1): default command to open files.
|
||||
* home (string): home page.
|
||||
* render_mode (string): default render mode to use ("fancy" or "dumb").
|
||||
|
||||
Notes:
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ def save_identities(identities: dict, identities_path: Path):
|
|||
"""Save the certificate stash. Return True on success."""
|
||||
try:
|
||||
with open(identities_path, "wt") as identities_file:
|
||||
json.dump(identities, identities_file)
|
||||
json.dump(identities, identities_file, indent=2)
|
||||
except (OSError, ValueError) as exc:
|
||||
logging.error(f"Failed to save identities '{identities_path}': {exc}")
|
||||
return False
|
||||
|
|
|
@ -35,18 +35,23 @@ class LineType(IntEnum):
|
|||
LIST_ITEM = 8
|
||||
|
||||
|
||||
def generate_metalines(elements, width):
|
||||
def generate_metalines(elements, width, dumb=False):
|
||||
"""Format elements into a list of lines with metadata.
|
||||
|
||||
The returned list ("metalines") are tuples (meta, line), meta being a
|
||||
dict of metadata and line a text line to display. Currently the only
|
||||
metadata keys used are:
|
||||
dict of metadata and a text line to display. Currently the only metadata
|
||||
keys used are:
|
||||
- type: one of the Renderer.TYPE constants.
|
||||
- url: only for links, the URL the link on this line refers to. Note
|
||||
that this key is present only for the first line of the link, i.e.
|
||||
long link descriptions wrapped on multiple lines will not have a this
|
||||
key except for the first line.
|
||||
- link_id: only alongside "url" key, ID generated for this link.
|
||||
|
||||
Arguments:
|
||||
- elements: list of elements to use.
|
||||
- width: max text width to use.
|
||||
- dumb: if True, standard presentation margins are ignored.
|
||||
"""
|
||||
metalines = []
|
||||
context = {"width": width}
|
||||
|
@ -78,12 +83,17 @@ def generate_metalines(elements, width):
|
|||
thin_type = LineType.LIST_ITEM
|
||||
else:
|
||||
continue
|
||||
# In dumb mode, elements producing no metalines still need to be
|
||||
# rendered as empty lines.
|
||||
if dumb:
|
||||
if not element_metalines:
|
||||
element_metalines = [({"type": LineType.PARAGRAPH}, "")]
|
||||
# If current element requires margins and is not the first elements,
|
||||
# separate from previous element. Also do it if the current element does
|
||||
# not require margins but follows an element that required it (e.g. link
|
||||
# after a paragraph). Also do it if both the current and previous
|
||||
# elements do not require margins but differ in type.
|
||||
if (
|
||||
elif (
|
||||
(has_margins and index > 0)
|
||||
or (not has_margins and previous_had_margins)
|
||||
or (not has_margins and thin_type != last_thin_type)
|
||||
|
|
|
@ -25,9 +25,9 @@ class MimeType:
|
|||
def from_str(mime_string) -> Optional["MimeType"]:
|
||||
"""Parse a MIME string into a MimeType instance, or None on error."""
|
||||
if ";" in mime_string:
|
||||
type_str, *parameters = mime_string.split(";")
|
||||
type_str, *param_strs = mime_string.split(";")
|
||||
parameters = {}
|
||||
for param in map(lambda s: s.strip().lower(), parameters):
|
||||
for param in map(lambda s: s.strip().lower(), param_strs):
|
||||
if param.count("=") != 1:
|
||||
return None
|
||||
param_name, param_value = param.split("=")
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from bebop.gemtext import parse_gemtext
|
||||
from bebop.metalines import generate_dumb_metalines, generate_metalines
|
||||
from bebop.mime import MimeType
|
||||
from bebop.links import Links
|
||||
|
||||
|
||||
|
@ -17,18 +19,25 @@ class Page:
|
|||
corresponding metalines, it is meant to be used as a quick map for link ID
|
||||
lookup and disambiguation.
|
||||
- title: optional page title.
|
||||
- mime: optional MIME type received from the server.
|
||||
- encoding: optional encoding received from the server.
|
||||
- render: optional render mode used to create the page from Gemtext.
|
||||
"""
|
||||
source: str
|
||||
metalines: list = field(default_factory=list)
|
||||
links: Links = field(default_factory=Links)
|
||||
title: str = ""
|
||||
mime: Optional[MimeType] = None
|
||||
encoding: str = ""
|
||||
render: Optional[str] = None
|
||||
|
||||
@staticmethod
|
||||
def from_gemtext(gemtext: str, wrap_at: int):
|
||||
def from_gemtext(gemtext: str, wrap_at: int, render: str ="fancy"):
|
||||
"""Produce a Page from a Gemtext file or string."""
|
||||
elements, links, title = parse_gemtext(gemtext)
|
||||
metalines = generate_metalines(elements, wrap_at)
|
||||
return Page(gemtext, metalines, links, title)
|
||||
dumb_mode = render == "dumb"
|
||||
elements, links, title = parse_gemtext(gemtext, dumb=dumb_mode)
|
||||
metalines = generate_metalines(elements, wrap_at, dumb=dumb_mode)
|
||||
return Page(gemtext, metalines, links, title, render=render)
|
||||
|
||||
@staticmethod
|
||||
def from_text(text: str):
|
||||
|
|
59
bebop/preferences.py
Normal file
59
bebop/preferences.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
"""Per-capsule preferences.
|
||||
|
||||
Currently contains only overrides for render modes, per URL path. In the future
|
||||
it may be interesting to move a few things from the config to here, such as the
|
||||
text width.
|
||||
|
||||
This is a map from an URL to dicts. The only used key is render_mode.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def load_capsule_prefs(prefs_path: Path) -> Optional[dict]:
|
||||
"""Return saved capsule preferences or None on error."""
|
||||
prefs = {}
|
||||
try:
|
||||
with open(prefs_path, "rt") as prefs_file:
|
||||
prefs = json.load(prefs_file)
|
||||
except (OSError, ValueError) as exc:
|
||||
logging.error(f"Failed to load capsule prefs '{prefs_path}': {exc}")
|
||||
return None
|
||||
return prefs
|
||||
|
||||
|
||||
def save_capsule_prefs(prefs: dict, prefs_path: Path):
|
||||
"""Save the capsule preferences. Return True on success."""
|
||||
try:
|
||||
with open(prefs_path, "wt") as prefs_file:
|
||||
json.dump(prefs, prefs_file, indent=2)
|
||||
except (OSError, ValueError) as exc:
|
||||
logging.error(f"Failed to save capsule prefs '{prefs_path}': {exc}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_url_render_mode_pref(prefs: dict, url: str, default: str):
|
||||
"""Return the desired render mode for this URL.
|
||||
|
||||
If the preferences contain the URL or a parent URL, the corresponding render
|
||||
mode is used. If several URLs are prefixes of the `url` argument, the
|
||||
longest one is used to get the matching preference.
|
||||
|
||||
Arguments:
|
||||
- prefs: current capsule preferences.
|
||||
- url: URL about to be rendered.
|
||||
- default: default render mode if no user preferences match.
|
||||
"""
|
||||
prefix_urls = []
|
||||
for key in prefs:
|
||||
if url.startswith(key):
|
||||
prefix_urls.append(key)
|
||||
if not prefix_urls:
|
||||
return default
|
||||
key = max(prefix_urls, key=len)
|
||||
preference = prefs[key]
|
||||
return preference.get("render_mode", default)
|
54
bebop/tests/test_preferences.py
Normal file
54
bebop/tests/test_preferences.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import unittest
|
||||
|
||||
from ..preferences import get_url_render_mode_pref
|
||||
|
||||
class TestPreferences(unittest.TestCase):
|
||||
|
||||
def test_get_url_render_mode_pref(self):
|
||||
prefs = {}
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com",
|
||||
"default"
|
||||
), "default")
|
||||
|
||||
prefs["gemini://example.com"] = {}
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com",
|
||||
"default"
|
||||
), "default")
|
||||
|
||||
prefs["gemini://example.com"] = {"render_mode": "test"}
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com",
|
||||
"default"
|
||||
), "test")
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/path",
|
||||
"default"
|
||||
), "test")
|
||||
|
||||
prefs["gemini://example.com/specific/subdir"] = {"render_mode": "test2"}
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/path",
|
||||
"default"
|
||||
), "test")
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/specific",
|
||||
"default"
|
||||
), "test")
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/specific/subdir",
|
||||
"default"
|
||||
), "test2")
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/specific/subdir/subsubdir",
|
||||
"default"
|
||||
), "test2")
|
|
@ -5,10 +5,13 @@ WELCOME_PAGE = """\
|
|||
|
||||
Hi! Welcome to the Bebop browser! 🚀🎶
|
||||
|
||||
Press "?" to see the keybinds, commands and more, or visit the link below by \
|
||||
pressing its link ID (1). To start browsing right away, press "o", type an URL \
|
||||
and press Enter.
|
||||
Press "?" or type ":help" and enter to see the keybinds, commands and more, \
|
||||
or visit the link below by pressing its link ID (1). To start browsing \
|
||||
right away, press "o", type an URL and press Enter.
|
||||
|
||||
=> bebop:help Help
|
||||
=> gemini://dece.space/dev/bebop.gmi Online documentation
|
||||
=> bebop:help Offline help page
|
||||
=> gemini://dece.space/dev/bebop.gmi Online Bebop home
|
||||
|
||||
You can configure which page to show up when starting Bebop instead of this \
|
||||
one: set your home URL in the "home" key of your configuration file.
|
||||
"""
|
||||
|
|
Reference in a new issue