Conversion of User Guide to the SHH stack (#2781)

This commit is contained in:
Adam Hopkins
2023-09-06 15:44:00 +03:00
committed by GitHub
parent 47215d4635
commit d255d1aae1
332 changed files with 51495 additions and 2013 deletions

View File

View File

@@ -0,0 +1,25 @@
from __future__ import annotations
from contextlib import contextmanager
from typing import Generator
from html5tagger import Builder
from sanic import Request
class BaseLayout:
def __init__(self, builder: Builder):
self.builder = builder
@contextmanager
def __call__(
self, request: Request, full: bool = True
) -> Generator[BaseLayout, None, None]:
with self.layout(request, full=full):
yield self
@contextmanager
def layout(
self, request: Request, full: bool = True
) -> Generator[None, None, None]:
yield

View File

@@ -0,0 +1,75 @@
from datetime import datetime
from html5tagger import Builder, E # type: ignore
from sanic import Request
def do_footer(builder: Builder, request: Request) -> None:
builder.footer(
_pagination(request),
_content(),
class_="footer",
)
def _pagination(request: Request) -> Builder:
return E.div(
_pagination_left(request), _pagination_right(request), class_="level"
)
def _pagination_left(request: Request) -> Builder:
item = E.div(class_="level-item")
if not hasattr(request.ctx, "previous_page"):
return E.div(item, class_="level-left")
with item:
if p := request.ctx.previous_page:
path = p.relative_path.with_suffix(".html")
item.a(
f"{p.meta.title}",
href=f"/{path}",
hx_get=f"/{path}",
hx_target="#content",
hx_swap="innerHTML",
hx_push_url="true",
class_="button pagination",
)
return E.div(item, class_="level-left")
def _pagination_right(request: Request) -> Builder:
item = E.div(class_="level-item")
if not hasattr(request.ctx, "next_page"):
return E.div(item, class_="level-right")
with item:
if p := request.ctx.next_page:
path = p.relative_path.with_suffix(".html")
item.a(
f"{p.meta.title}",
href=f"/{path}",
hx_get=f"/{path}",
hx_target="#content",
hx_swap="innerHTML",
hx_push_url="true",
class_="button pagination",
)
return E.div(item, class_="level-right")
def _content() -> Builder:
year = datetime.now().year
inner = E.p(
E.a(
"MIT Licensed",
href="https://github.com/sanic-org/sanic/blob/master/LICENSE",
target="_blank",
rel="nofollow noopener noreferrer",
).br()(
E.small(f"Copyright © 2018-{year} Sanic Community Organization")
),
)
return E.div(
inner,
E.p("~ Made with ❤️ and ☕️ ~"),
class_="content has-text-centered",
)

View File

@@ -0,0 +1,68 @@
from webapp.display.layouts.models import MenuItem
from html5tagger import Builder, E # type: ignore
from sanic import Request
def do_navbar(builder: Builder, request: Request) -> None:
navbar_items = [
_render_navbar_item(item, request)
for item in request.app.config.NAVBAR
]
container = E.div(
_search_form(request), *navbar_items, class_="navbar-end"
)
builder.nav(
E.div(container, class_="navbar-menu"),
class_="navbar is-hidden-touch",
)
def _search_form(request: Request) -> Builder:
return E.div(
E.div(
E.input(
id_="search",
type_="text",
placeholder="Search",
class_="input",
value=request.args.get("q", ""),
hx_target="#content",
hx_swap="innerHTML",
hx_push_url="true",
hx_trigger="keyup changed delay:500ms",
hx_get=f"/{request.ctx.language}/search",
hx_params="*",
),
class_="control",
),
class_="navbar-item",
)
def _render_navbar_item(item: MenuItem, request: Request) -> Builder:
if item.items:
return E.div(
E.a(item.label, class_="navbar-link"),
E.div(
*(
_render_navbar_item(subitem, request)
for subitem in item.items
),
class_="navbar-dropdown",
),
class_="navbar-item has-dropdown is-hoverable",
)
kwargs = {
"class_": "navbar-item",
}
if item.href:
kwargs["href"] = item.href
kwargs["target"] = "_blank"
kwargs["rel"] = "nofollow noopener noreferrer"
elif item.path:
kwargs["href"] = f"/{request.ctx.language}/{item.path}"
internal = [item.label]
return E.a(*internal, **kwargs)

View File

@@ -0,0 +1,118 @@
from webapp.display.layouts.models import MenuItem
from webapp.display.text import slugify
from html5tagger import Builder, E # type: ignore
from sanic import Request
def do_sidebar(builder: Builder, request: Request) -> None:
builder.a(class_="burger")(E.span().span().span().span())
builder.aside(*_menu_items(request), class_="menu")
def _menu_items(request: Request) -> list[Builder]:
return [
_sanic_logo(request),
*_sidebar_items(request),
E.hr(),
E.p("Current with version ").strong(
request.app.config.GENERAL.current_version
),
E.hr(),
E.p("Want more? ").a(
"sanicbook.com", href="https://sanicbook.com", target="_blank"
),
]
def _sanic_logo(request: Request) -> Builder:
return E.a(
class_="navbar-item sanic-simple-logo",
href=f"https://sanic.dev/{request.ctx.language}/",
)(
E.img(
src="/assets/images/sanic-framework-logo-simple-400x97.png", # noqa: E501
alt="Sanic Framework",
)
)
def _sidebar_items(request: Request) -> list[Builder]:
return [
builder
for item in request.app.config.SIDEBAR
for builder in _render_sidebar_item(item, request, True)
]
def _render_sidebar_item(
item: MenuItem, request: Request, root: bool = False
) -> list[Builder]:
builders: list[Builder] = []
if root:
builders.append(E.p(class_="menu-label")(item.label))
else:
builders.append(_single_sidebar_item(item, request))
if item.items:
ul = E.ul(class_="menu-list")
with ul:
for subitem in item.items:
sub_builders = _render_sidebar_item(subitem, request)
ul(*sub_builders)
builders.append(ul)
return builders
def _single_sidebar_item(item: MenuItem, request: Request) -> Builder:
if item.path and item.path.startswith("/"):
path = item.path
else:
path = f"/{request.ctx.language}/{item.path}" if item.path else ""
kwargs = {}
classes: list[str] = []
li_classes = "menu-item"
_, page, _ = request.app.ctx.get_page(
request.ctx.language, item.path or ""
)
if request.path == path:
classes.append("is-active")
if item.href:
kwargs["href"] = item.href
kwargs["target"] = "_blank"
kwargs["rel"] = "nofollow noopener noreferrer"
elif not path:
li_classes += " is-group"
if _is_open_item(item, request.ctx.language, request.path):
classes.append("is-open")
else:
kwargs.update(
{
"href": path,
"hx-get": path,
"hx-target": "#content",
"hx-swap": "innerHTML",
"hx-push-url": "true",
}
)
kwargs["class_"] = " ".join(classes)
inner = E().a(item.label, **kwargs)
if page and page.anchors:
with inner.ul(class_="anchor-list"):
for anchor in page.anchors:
inner.li(
E.a(anchor.strip("`"), href=f"{path}#{slugify(anchor)}"),
class_="is-anchor",
)
return E.li(inner, class_=li_classes)
def _is_open_item(item: MenuItem, language: str, current_path: str) -> bool:
path = f"/{language}/{item.path}" if item.path else ""
if current_path == path:
return True
for subitem in item.items:
if _is_open_item(subitem, language, current_path):
return True
return False

View File

@@ -0,0 +1,50 @@
from __future__ import annotations
from contextlib import contextmanager
from typing import Generator
from html5tagger import Builder, E
from sanic import Request
from .base import BaseLayout
class HomeLayout(BaseLayout):
@contextmanager
def layout(
self, request: Request, full: bool = True
) -> Generator[None, None, None]:
self._hero(request.ctx.language)
with self.builder.div(class_="container"):
yield
def _hero(self, language: str) -> None:
with self.builder.section(class_="hero is-large has-text-centered"):
self.builder.div(
E.h1(E.span("Sanic"), class_="title"),
E.h2(class_="subtitle")("Build fast. Run fast."),
E.h3(class_="tagline")("Accelerate your web app development"),
self._do_buttons(language),
class_="hero-body",
)
def _do_buttons(self, language: str) -> Builder:
builder = E.div(class_="buttons is-centered")
with builder:
builder.a(
"Get Started",
class_="button is-primary",
href=f"/{language}/guide/getting-started.html",
)
builder.a(
"Help",
class_="button is-outlined",
href=f"/{language}/help.html",
)
builder.a(
"GitHub",
class_="button is-outlined",
href="https://github.com/sanic-org/sanic",
target="_blank",
)
return builder

View File

@@ -0,0 +1,45 @@
from contextlib import contextmanager
from typing import Generator
from webapp.display.layouts.elements.footer import do_footer
from webapp.display.layouts.elements.navbar import do_navbar
from webapp.display.layouts.elements.sidebar import do_sidebar
from sanic import Request
from .base import BaseLayout
class MainLayout(BaseLayout):
@contextmanager
def layout(
self, request: Request, full: bool = True
) -> Generator[None, None, None]:
if full:
with self.builder.div(class_="is-flex"):
self._sidebar(request)
with self.builder.main(class_="is-flex-grow-1"):
self._navbar(request)
with self.builder.div(class_="container", id="content"):
with self._content_wrapper():
yield
self._footer(request)
else:
with self._content_wrapper():
yield
self._footer(request)
@contextmanager
def _content_wrapper(self) -> Generator[None, None, None]:
with self.builder.section(class_="section"):
with self.builder.article():
yield
def _navbar(self, request: Request) -> None:
do_navbar(self.builder, request)
def _sidebar(self, request: Request) -> None:
do_sidebar(self.builder, request)
def _footer(self, request: Request) -> None:
do_footer(self.builder, request)

View File

@@ -0,0 +1,14 @@
from __future__ import annotations
from msgspec import Struct, field
class MenuItem(Struct, kw_only=False, omit_defaults=True):
label: str
path: str | None = None
href: str | None = None
items: list[MenuItem] = field(default_factory=list)
class GeneralConfig(Struct, kw_only=False):
current_version: str