added config option for preview scroll tolerance

This commit is contained in:
Daniel Fichtinger 2025-07-13 17:31:25 -04:00
parent fe0f338803
commit 10d1772a2d
5 changed files with 217 additions and 175 deletions

View file

@ -95,14 +95,15 @@ def serve(
bool,
typer.Option("--final", "-f", help="Don't include drafts."),
] = False,
no_live_reload: Annotated[
bool,
live_reload: Annotated[
bool | None,
typer.Option(
"--no-live-reload",
"-n",
help="Don't automatically reload web preview.",
"--live-reload/--no-live-reload",
"-l/-L",
help="Automatically reload web preview. Overrides config.",
show_default=False,
),
] = False,
] = None,
):
"""
Build the website and start a live preview server.
@ -115,13 +116,17 @@ def serve(
print("Preview without drafts.")
else:
print("Preview with drafts.")
if live_reload is None:
reload = None
else:
reload = live_reload
server.serve(
root=root,
output=output,
draft=not final,
host=host,
port=port,
live_reload=not no_live_reload,
user_reload=reload,
)

View file

@ -58,6 +58,17 @@ class BuildConfig:
include_drafts: bool = False
@dataclass
class ReloadConfig:
enabled: bool = True
scroll_tolerance: int = 100
@dataclass
class ServerConfig:
reload: ReloadConfig = field(default_factory=ReloadConfig)
IGNORELIST = [".marksman.toml"]
@ -71,6 +82,7 @@ class ZonaConfig:
markdown: MarkdownConfig = field(default_factory=MarkdownConfig)
build: BuildConfig = field(default_factory=BuildConfig)
blog: BlogConfig = field(default_factory=BlogConfig)
server: ServerConfig = field(default_factory=ServerConfig)
@classmethod
def from_file(cls, path: Path) -> "ZonaConfig":

View file

@ -9,12 +9,13 @@
});
}
const ws = new WebSocket("__SOCKET_ADDRESS_");
const ws = new WebSocket("__SOCKET_ADDRESS__");
const tol = __SCROLL_TOLERANCE__;
ws.onmessage = event => {
if (event.data === "reload") {
// store flag if user currently at bottom
const nearBottom = window.innerHeight + window.scrollY
>= document.body.scrollHeight - 100;
>= document.body.scrollHeight - tol;
if (nearBottom) {
localStorage.setItem("wasAtBottom", "1");
}

View file

@ -21,17 +21,22 @@ from zona.websockets import WebSocketServer
logger = get_logger()
def make_reload_script(host: str, port: int) -> str:
def make_reload_script(
host: str, port: int, scroll_tolerance: int
) -> str:
"""Generates the JavaScript that must be injected into HTML pages for the live reloading to work."""
js = util.get_resource("server/inject.js").contents
js = util.minify_js(js)
address = f"ws://{host}:{port}"
for placeholder, value in (("__SOCKET_ADDRESS_", address),):
for placeholder, value in (
("__SOCKET_ADDRESS__", address),
("__SCROLL_TOLERANCE__", scroll_tolerance),
):
if placeholder not in js:
raise ValueError(
f"{placeholder} missing from reload script template!"
)
js = js.replace(placeholder, value)
js = js.replace(placeholder, str(value))
return f"<script>{js}</script>"
@ -172,12 +177,13 @@ def serve(
draft: bool = True,
host: str = "localhost",
port: int = 8000,
live_reload: bool = True,
user_reload: bool | None = None,
):
"""Serve preview website with live reload and automatic rebuild."""
# create temp dir, automatic cleanup
with tempfile.TemporaryDirectory() as tmp:
builder = ZonaBuilder(root, Path(tmp), draft)
config = builder.config
# initial site build
builder.build()
# use discovered paths if none provided
@ -186,13 +192,21 @@ def serve(
if root is None:
root = builder.layout.root
# spin up websocket server for live reloading
if live_reload:
# use config value unless overridden by user
reload = config.server.reload.enabled
if user_reload is not None:
reload = user_reload
if reload:
print("Live reloading is enabled.")
# spin up websocket server for live reloading
ws_port = port + 1
ws_server = WebSocketServer(host, ws_port)
ws_server.start()
# generate reload script for injection
reload_script = make_reload_script(host, ws_port)
scroll_tolerance = config.server.reload.scroll_tolerance
reload_script = make_reload_script(
host, ws_port, scroll_tolerance
)
# generate handler with reload script as attribute
handler = make_handler_class(reload_script)
else: