diff --git a/.gitignore b/.gitignore
index 29aa1ae..0a84836 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.vscode/
node_modules/
_site/
# Byte-compiled / optimized / DLL files
diff --git a/examples/app.py b/examples/app.py
index 0f190bf..ba5ecdc 100644
--- a/examples/app.py
+++ b/examples/app.py
@@ -2,6 +2,7 @@
from fastapi.staticfiles import StaticFiles
from starlette.routing import Route
from pyview import PyView, defaultRootTemplate
+from markupsafe import Markup
from .views import (
CountLiveView,
@@ -24,7 +25,12 @@
"""
-app.rootTemplate = defaultRootTemplate(css)
+
+def content_wrapper(_context, content: Markup) -> Markup:
+ return Markup("Home") + content
+
+
+app.rootTemplate = defaultRootTemplate(css=Markup(css), content_wrapper=content_wrapper)
routes = [
(
diff --git a/pyproject.toml b/pyproject.toml
index 8fe3110..92a90fa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ packages = [
{ include = "pyview" },
]
-version = "0.0.7a"
+version = "0.0.8a"
description = "LiveView in Python"
authors = ["Larry Ogrodnek "]
license = "MIT"
diff --git a/pyview/pyview.py b/pyview/pyview.py
index 06a996d..56daab3 100644
--- a/pyview/pyview.py
+++ b/pyview/pyview.py
@@ -14,19 +14,7 @@
from .ws_handler import LiveSocketHandler
from .live_view import LiveView
from .live_routes import LiveViewLookup
-from typing import Callable, Optional, TypedDict
-
-
-class RootTemplateContext(TypedDict):
- id: str
- content: str
- title: Optional[str]
- css: Optional[str]
- csrf_token: str
- session: Optional[str]
-
-
-RootTemplate = Callable[[RootTemplateContext], str]
+from .template import RootTemplate, RootTemplateContext, defaultRootTemplate
class PyView(Starlette):
@@ -34,7 +22,7 @@ class PyView(Starlette):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.rootTemplate = defaultRootTemplate("")
+ self.rootTemplate = defaultRootTemplate()
self.view_lookup = LiveViewLookup()
self.live_handler = LiveSocketHandler(self.view_lookup)
@@ -46,14 +34,18 @@ async def live_websocket_endpoint(websocket: WebSocket):
def add_live_view(self, path: str, view: type[LiveView]):
async def lv(request: Request):
- return await liveview_container(self.rootTemplate, self.view_lookup, request)
+ return await liveview_container(
+ self.rootTemplate, self.view_lookup, request
+ )
self.view_lookup.add(path, view)
auth = AuthProviderFactory.get(view)
self.routes.append(Route(path, auth.wrap(lv), methods=["GET"]))
-async def liveview_container(template: RootTemplate, view_lookup: LiveViewLookup, request: Request):
+async def liveview_container(
+ template: RootTemplate, view_lookup: LiveViewLookup, request: Request
+):
url = request.url
path = url.path
lv: LiveView = view_lookup.get(path)
@@ -72,49 +64,7 @@ async def liveview_container(template: RootTemplate, view_lookup: LiveViewLookup
"content": r.text(),
"title": s.live_title,
"csrf_token": generate_csrf_token("lv:phx-" + id),
- "css": None,
"session": serialize_session(session),
}
return HTMLResponse(template(context))
-
-
-def defaultRootTemplate(css: str) -> RootTemplate:
- def template(context: RootTemplateContext) -> str:
- context["css"] = css
- return _defaultRootTemplate(context)
-
- return template
-
-
-def _defaultRootTemplate(context: RootTemplateContext) -> str:
- suffix = " | LiveView"
- render_title = (context["title"] + suffix) if context.get("title", None) is not None else "LiveView" # type: ignore
- css = context["css"] if context.get("css", None) is not None else ""
- return f"""
-
-
-
- {render_title}
-
-
-
-
-
- {css}
-
-
-
-
Home
-
- {context['content']}
-
-
-
-
-"""
diff --git a/pyview/template/__init__.py b/pyview/template/__init__.py
index 73b978f..f7db6e3 100644
--- a/pyview/template/__init__.py
+++ b/pyview/template/__init__.py
@@ -1,2 +1,3 @@
from pyview.vendor.ibis import Template
from .live_template import LiveTemplate, template_file, RenderedContent, LiveRender
+from .root_template import RootTemplate, RootTemplateContext, defaultRootTemplate
diff --git a/pyview/template/root_template.py b/pyview/template/root_template.py
new file mode 100644
index 0000000..b023a68
--- /dev/null
+++ b/pyview/template/root_template.py
@@ -0,0 +1,71 @@
+from typing import Callable, Optional, TypedDict
+from markupsafe import Markup
+
+
+class RootTemplateContext(TypedDict):
+ id: str
+ content: str
+ title: Optional[str]
+ csrf_token: str
+ session: Optional[str]
+
+
+RootTemplate = Callable[[RootTemplateContext], str]
+ContentWrapper = Callable[[RootTemplateContext, Markup], Markup]
+
+
+def defaultRootTemplate(
+ css: Optional[Markup] = None, content_wrapper: Optional[ContentWrapper] = None
+) -> RootTemplate:
+ content_wrapper = content_wrapper or (lambda c, m: m)
+
+ def template(context: RootTemplateContext) -> str:
+ return _defaultRootTemplate(context, css or Markup(""), content_wrapper)
+
+ return template
+
+
+def _defaultRootTemplate(
+ context: RootTemplateContext, css: Markup, contentWrapper: ContentWrapper
+) -> str:
+ suffix = " | LiveView"
+ render_title = (context["title"] + suffix) if context.get("title", None) is not None else "LiveView" # type: ignore
+ main_content = contentWrapper(
+ context,
+ Markup(
+ f"""
+
+ {context['content']}
+
"""
+ ),
+ )
+
+ return (
+ Markup(
+ f"""
+
+
+
+ {render_title}
+
+
+
+
+
+ {css}
+
+ """
+ )
+ + main_content
+ + Markup(
+ """
+
+
+"""
+ )
+ )