Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 6 additions & 23 deletions docs/app/agent_files/_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,33 +112,16 @@ def _extract_markdown_title(source: str) -> str | None:

def _llms_url_for_path(url_path: Path) -> str:
"""Return the public URL for a generated markdown asset."""
from reflex_base.config import get_config

config = get_config()
deploy_url = config.deploy_url.removesuffix("/") if config.deploy_url else ""
frontend_path = (config.frontend_path or "").strip("/")
base_url = deploy_url
if frontend_path:
base_url = f"{base_url}/{frontend_path}" if base_url else f"/{frontend_path}"
return (
f"{base_url}/{url_path.as_posix()}" if base_url else f"/{url_path.as_posix()}"
)
from reflex_site_shared.utils.url import public_url

return public_url(f"/{url_path.as_posix()}")


def _docs_home_url() -> str:
"""Return the public URL for the docs home."""
from reflex_base.config import get_config

config = get_config()
deploy_url = config.deploy_url.removesuffix("/") if config.deploy_url else ""
frontend_path = (config.frontend_path or "").strip("/")
if deploy_url and frontend_path:
return f"{deploy_url}/{frontend_path}/"
if deploy_url:
return f"{deploy_url}/"
if frontend_path:
return f"/{frontend_path}/"
return "/"
from reflex_site_shared.utils.url import public_url

return public_url("/")


def _strip_first_heading(source: str) -> str:
Expand Down
72 changes: 60 additions & 12 deletions docs/app/reflex_docs/pages/docs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import os
from collections import defaultdict, namedtuple
from functools import lru_cache
from pathlib import Path
from types import SimpleNamespace

import reflex as rx
from reflex_components_core.core.cond import Cond
from reflex_docgen.markdown import parse_document
from reflex_docgen.markdown import FrontMatter, parse_document

# External Components
from reflex_pyplot import pyplot as pyplot
Expand All @@ -24,6 +25,8 @@
from .library import library
from .recipes_overview import overview

DEFAULT_DOC_DESCRIPTION_TEMPLATE = "Reflex Documentation page for {title}"

SPECIAL_COMPONENT_DOCS = {
"rx.cond": Cond,
}
Expand Down Expand Up @@ -62,14 +65,20 @@ def build_nested_namespace(
return parent_namespace


@lru_cache(maxsize=None)
def _frontmatter_for(filepath: str) -> FrontMatter | None:
"""Parse a doc's frontmatter once per file (cached for the process lifetime)."""
source = Path(filepath).read_text(encoding="utf-8")
return parse_document(source).frontmatter


def get_components_from_frontmatter(filepath: str) -> list:
"""Extract component tuples from a doc's frontmatter."""
source = Path(filepath).read_text(encoding="utf-8")
doc = parse_document(source)
if doc.frontmatter is None:
fm = _frontmatter_for(filepath)
if fm is None:
return []
components = []
for comp_str in doc.frontmatter.components:
for comp_str in fm.components:
if component := SPECIAL_COMPONENT_DOCS.get(comp_str):
components.append((component, comp_str))
continue
Expand All @@ -87,11 +96,28 @@ def get_components_from_frontmatter(filepath: str) -> list:

def get_previews_from_frontmatter(filepath: str) -> dict[str, str]:
"""Extract component preview sources from a doc's frontmatter."""
source = Path(filepath).read_text(encoding="utf-8")
doc = parse_document(source)
if doc.frontmatter is None:
fm = _frontmatter_for(filepath)
if fm is None:
return {}
return {p.name: p.source for p in doc.frontmatter.component_previews}
return {p.name: p.source for p in fm.component_previews}


def get_description_from_frontmatter(filepath: str, title: str) -> str:
"""Resolve a per-page meta description for a doc.

Uses the ``description`` frontmatter field when set; otherwise falls back
to ``DEFAULT_DOC_DESCRIPTION_TEMPLATE``.
"""
fm = _frontmatter_for(filepath)
if fm is not None and fm.description:
return fm.description
return DEFAULT_DOC_DESCRIPTION_TEMPLATE.format(title=title)


def get_image_from_frontmatter(filepath: str) -> str | None:
"""Resolve a per-page social preview image from frontmatter, if any."""
fm = _frontmatter_for(filepath)
return fm.image if fm is not None else None


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -193,13 +219,22 @@ def resolve_doc_route(doc: str, title: str) -> ResolvedDoc | None:
return ResolvedDoc(route=route, display_title=display_title, category=category)


def make_docpage(route: str, title: str, doc_virtual: str, render_fn):
def make_docpage(
route: str,
title: str,
doc_virtual: str,
render_fn,
description: str | None = None,
image: str | None = None,
):
"""Wrap a render function as a docpage, setting module metadata."""
doc_path = Path(doc_virtual)
render_fn.__module__ = ".".join(doc_path.parts[:-1])
render_fn.__name__ = doc_path.stem
render_fn.__qualname__ = doc_path.stem
return docpage(set_path=route, t=title)(render_fn)
return docpage(set_path=route, t=title, description=description, image=image)(
render_fn
)


def handle_library_doc(
Expand All @@ -211,6 +246,8 @@ def handle_library_doc(
"""Handle docs/library/** docs — component API reference via multi_docs."""
clist = [title, *get_components_from_frontmatter(actual_path)]
previews = get_previews_from_frontmatter(actual_path)
description = get_description_from_frontmatter(actual_path, resolved.display_title)
image = get_image_from_frontmatter(actual_path)
if doc.startswith("docs/library/graphing"):
graphing_components[resolved.category].append(clist)
else:
Expand All @@ -222,6 +259,8 @@ def handle_library_doc(
previews=previews,
component_list=clist,
title=resolved.display_title,
description=description,
image=image,
)


Expand All @@ -242,7 +281,16 @@ def comp(_actual=actual_path, _virtual=virtual_doc):
)
return ((toc, doc_content), rendered)

return make_docpage(resolved.route, resolved.display_title, virtual_doc, comp)
description = get_description_from_frontmatter(actual_path, resolved.display_title)
image = get_image_from_frontmatter(actual_path)
return make_docpage(
resolved.route,
resolved.display_title,
virtual_doc,
comp,
description=description,
image=image,
)


# Build doc_markdown_sources mapping
Expand Down
11 changes: 9 additions & 2 deletions docs/app/reflex_docs/pages/docs/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,8 @@ def multi_docs(
previews: dict[str, str],
component_list: list,
title: str,
description: str | None = None,
image: str | None = None,
):
components = [
component_docs(component_tuple, previews)
Expand Down Expand Up @@ -986,7 +988,7 @@ def links(current_page, ll_doc_exists, path):
)
return rx.fragment()

@docpage(set_path=path, t=title)
@docpage(set_path=path, t=title, description=description, image=image)
def out():
toc = get_docgen_toc(actual_path)
doc_content = Path(actual_path).read_text(encoding="utf-8")
Expand All @@ -1012,7 +1014,12 @@ def out():
class_name="flex flex-col w-full",
)

@docpage(set_path=path + "low", t=title + " (Low Level)")
@docpage(
set_path=path + "low",
t=title + " (Low Level)",
description=description,
image=image,
)
def ll():
ll_virtual = virtual_path.replace(".md", "-ll.md")
toc = get_docgen_toc(ll_actual_path)
Expand Down
85 changes: 59 additions & 26 deletions docs/app/reflex_docs/reflex_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,43 @@
from reflex_site_shared import styles
from reflex_site_shared.backend.status import monitor_checkly_status
from reflex_site_shared.constants import REFLEX_ASSETS_CDN
from reflex_site_shared.meta.meta import favicons_links, to_cdn_image_url
from reflex_site_shared.meta.meta import (
ONE_LINE_DESCRIPTION,
create_meta_tags,
favicons_links,
to_cdn_image_url,
)
from reflex_site_shared.telemetry import get_pixel_website_trackers
from reflex_site_shared.utils.url import public_url

from reflex_docs.pages import page404, routes
from reflex_docs.templates.docpage.docpage import DOCS_PROD_BASE
from reflex_docs.whitelist import _check_whitelisted_path


def _seo_meta_tags(
title: str, description: str, image: str, canonical_url: str
) -> list:
"""Build the per-page SEO meta tag set, deduped against framework output.

Reflex's ``add_page`` already emits ``<meta name="description">`` (from
its ``description=`` arg) and ``<meta property="og:image">`` (from its
``image=`` arg). We strip those entries from ``create_meta_tags`` to
avoid duplicate tags in the rendered ``<head>``.
"""
tags = create_meta_tags(
title=title, description=description, image=image, url=canonical_url
)
return [
t
for t in tags
if not (
isinstance(t, dict)
and (t.get("name") == "description" or t.get("property") == "og:image")
)
]
Comment thread
carlosabadia marked this conversation as resolved.


# This number discovered by trial and error on Windows 11 w/ Node 18, any
# higher and the prod build fails with EMFILE error.
WINDOWS_MAX_ROUTES = int(os.environ.get("REFLEX_WEB_WINDOWS_MAX_ROUTES", "100"))
Expand Down Expand Up @@ -76,37 +107,39 @@ def _llms_txt_directive() -> rx.Component:
routes = routes[:WINDOWS_MAX_ROUTES]

# Add the pages to the app.
_DEFAULT_PREVIEW = f"{REFLEX_ASSETS_CDN}previews/index_preview.webp"
for route in routes:
# print(f"Adding route: {route}")
if _check_whitelisted_path(route.path):
# Normalize image to CDN URL when it's a relative path
image_url = (
f"{REFLEX_ASSETS_CDN}previews/index_preview.webp"
if route.image is None
else to_cdn_image_url(route.image)
or f"{REFLEX_ASSETS_CDN}previews/index_preview.webp"
)

page_args = {
"component": route.component,
"route": route.path,
"title": route.title,
"image": image_url,
"meta": [
{"name": "theme-color", "content": route.background_color},
],
"on_load": route.on_load,
}

# Add the description only if it is not None
if route.description is not None:
page_args["description"] = route.description
# Add the extra meta data only if it is not None
to_cdn_image_url(route.image) if route.image else None
) or _DEFAULT_PREVIEW
page_description = route.description or ONE_LINE_DESCRIPTION

meta_tags: list = [
{"name": "theme-color", "content": route.background_color},
]
if isinstance(route.title, str):
meta_tags.extend(
_seo_meta_tags(
title=route.title,
description=page_description,
image=image_url,
canonical_url=public_url(route.path, fallback_base=DOCS_PROD_BASE),
)
)
if route.meta is not None:
page_args["meta"].extend(route.meta)
meta_tags.extend(route.meta)

# Call add_page with the dynamically constructed arguments
app.add_page(**page_args)
app.add_page(
component=route.component,
route=route.path,
title=route.title,
description=page_description,
image=image_url,
meta=meta_tags,
on_load=route.on_load,
)

# Add redirects.
redirects = [
Expand Down
17 changes: 10 additions & 7 deletions docs/app/reflex_docs/templates/docpage/docpage.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,8 @@ def docpage(
right_sidebar: bool = True,
page_title: str | None = None,
pseudo_right_bar: bool = False,
description: str | None = None,
image: str | None = None,
):
"""A template that most pages on the reflex.dev site should use.

Expand All @@ -698,6 +700,8 @@ def docpage(
right_sidebar: Whether to show the right sidebar.
page_title: The full title to set for the page. If None, defaults to `{title} · Reflex Docs`.
pseudo_right_bar: Whether to show a pseudo right sidebar (empty space).
description: SEO meta description for the page.
image: Social-preview image (relative path or absolute URL).

Returns:
A wrapper function that returns the full webpage.
Expand Down Expand Up @@ -941,15 +945,14 @@ def wrapper(*args, **kwargs) -> rx.Component:
if len(components) > 2
else None
)
if page_title:
return Route(
path=path,
title=page_title,
component=wrapper,
)
resolved_title = page_title or (
f"{title} · Reflex Docs" if category is None else title
)
return Route(
path=path,
title=f"{title} · Reflex Docs" if category is None else title,
title=resolved_title,
description=description,
image=image,
component=wrapper,
)

Expand Down
Loading
Loading