This Site Is Now Powered by This Notebook

by Audrey M. Roy Greenfeld | Thu, Jan 23, 2025

Here in this Jupyter notebook I rewrite audrey.feldroy.com and use nb_export to export it as my new main.py for arg-blog-fasthtml.


#| default_exp main
#| export
from datetime import datetime
from execnb.nbio import read_nb
from nb2fasthtml.core import render_code_output
from fasthtml.common import *
from fasthtml.jupyter import *
from IPython.display import display, HTML
from monsterui import franken
from monsterui.all import Theme
import mistletoe
import pygments
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
tags = ["Python", "Jupyter"]

Background

Before this notebook, the code for my blog was a main.py that I maintained by editing it in VS Code. The index page and notebooks were rendered without MonsterUI. I wanted to keep it minimal because sometimes I do crazy CSS experiments.

Yesterday in Customizing FastHTML Headers From Notebook Contents I discovered that different routes can have different HTML headers thanks to _xt_cts. I then modified my notebook detail view to include MonsterUI headers only when I tag the notebook with "MonsterUI".

Today I was inspired by Solve It With Code Lesson 11 to try rewriting more of my blog in MonsterUI.

Setup

#| export
app,rt = fast_app(pico=False)
server = JupyUvi(app)

Notebook Index: Cards

def NBCard(t,d):
    return franken.Card(d, header=franken.H3(t))
NBCard("First Post", "This is a description of what my post is about")
show(NBCard("First Post", "This is a description of what my post is about"))
#| export
def get_nb_paths(): return L(sorted(Path().glob("nbs/*.ipynb"), reverse=True))

Note: change to glob("*.ipynb") to run this from the notebook

nb_paths = get_nb_paths()
nb_paths
read_nb(nb_paths[0]).cells[0].source.lstrip("# ")

This is the title of the first notebook.

#| export
def get_title_and_desc(fpath):
    nb = read_nb(fpath)
    title = nb.cells[0].source.lstrip("# ")
    desc = nb.cells[1].source
    return title,desc
get_title_and_desc(nb_paths[0])
show(NBCard(*get_title_and_desc(nb_paths[0])))
def mk_nbcard_from_nb_path(nb_path):
    return NBCard(*get_title_and_desc(nb_path))
mk_nbcard_from_nb_path(nb_paths[0])
nb_paths.map(mk_nbcard_from_nb_path)[:2]
show(*nb_paths.map(mk_nbcard_from_nb_path)[:2])

Notebook Index: Add Dates to Cards

#| export
def get_date_from_iso8601_prefix(fname):
    "Gets date from first 10 chars YYYY-MM-DD of `fname`, where `fname` is like `2025-01-12-Get-Date-From-This.whatever"
    try:
        return datetime.fromisoformat(str(fname)[4:14])
    except ValueError: return None
nb_paths[0]
date = get_date_from_iso8601_prefix(nb_paths[0])
Div(f"{date:%a, %b %-d, %Y}", style="font-size: 0.875rem;color:#666;")
franken.PSmall(f"{date:%a, %b %-d, %Y}", cls="uk-text-muted")
def NBCard(t,d):
    return franken.Card(
        franken.CardTitle(franken.H3(t)), 
        franken.PSmall(f"{date:%a, %b %-d, %Y}", cls="uk-text-muted"),
        franken.P(d),
        body_cls='space-y-2'
    )
c = mk_nbcard_from_nb_path(nb_paths[0])
c
show(c)

Notebook Index: Linkify Cards

#| export
def NBCard(title,desc,href,date):
    return A(
        franken.Card(
        franken.CardTitle(franken.H3(title)), 
        franken.PSmall(f"{date:%a, %b %-d, %Y}", cls="uk-text-muted"),
        franken.P(desc),
        body_cls='space-y-2'
    ), href=href)
#| export
def mk_nbcard_from_nb_path(nb_path):
    date = get_date_from_iso8601_prefix(nb_path)
    return NBCard(*get_title_and_desc(nb_path), href=f'/nbs/{nb_path.name[:-6]}', date=date)
c = mk_nbcard_from_nb_path(nb_paths[1])
c
show(c)

Notebook Index Page

#| export
@rt
def index():
    nb_paths = get_nb_paths()
    return (
        Theme.blue.headers(),
        franken.Container(
            Div(
                franken.H1('audrey.feldroy.com'), franken.PParagraph("The experimental Jupyter notebooks of Audrey M. Roy Greenfeld. This website and all its notebooks are open-source at ", franken.A("github.com/audreyfeldroy/arg-blog-fasthtml", href="https://github.com/audreyfeldroy/arg-blog-fasthtml"), cls="mb-6"),
            ),
            franken.Grid(*nb_paths.map(mk_nbcard_from_nb_path), cols_sm=1, cols_md=1, cols_lg=2, cols_xl=3)
        )
    )

Notebook Detail: Frontmatter

t,d = get_title_and_desc(nb_paths[1])
t,d
mistletoe.markdown(d)

Notebook Detail: Non-Frontmatter

nb_paths[1]

Use StyledCode (Pygments) for Code Cells

nb = read_nb(nb_paths[1])
nb.cells[5]
#| export
def StyledCode(c, style='monokai'):
    fm = HtmlFormatter(style=style, cssclass=style)
    h = highlight(c, PythonLexer(), fm)
    sd = fm.get_style_defs(f".{style}")
    return Div(Style(sd), NotStr(h), id=style)
HTML(to_xml(StyledCode(nb.cells[2].source)))
HTML(to_xml(StyledCode(nb.cells[5].source)))

Use Mistletoe for Markdown Cells

nb.cells[4]
#| export
def StyledMd(m):
    return Safe(mistletoe.markdown(m))
HTML(StyledMd(nb.cells[4].source))

Use Mistletoe for Output Data of Code Cells

HTML(to_xml(StyledMd(nb.cells[5].outputs[0].data["text/markdown"])))
#| export
def StyledCell(c):
    if c.cell_type == "markdown": return StyledMd(c.source)
    if c.cell_type == "code": 
        if not c.outputs: return StyledCode(c.source)
        return StyledCode(c.source), render_code_output(c)
L(nb.cells).map(StyledCell)
show(*L(nb.cells).map(StyledCell))

Notebook Detail Page

#| export
@rt("/nbs/{name}")
def notebook(name:str):
    fpath = Path(f'nbs/{name}.ipynb')
    nb = read_nb(fpath)
    title = nb.cells[0].source.lstrip("# ")
    desc = nb.cells[1].source
    if "MonsterUI" in title:
        return (
            Theme.slate.headers(),
            franken.Container(
                franken.H1(title), # Title
                franken.P(desc), # Desc
                *L(nb.cells[2:]).map(StyledCell),
                cls="space-y-5"
            )
    )
    return (
        Style(':root {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; color-scheme: light dark;} body {background-color: light-dark(#ffffff, #1a1a1a); color: light-dark(#1a1a1a, #ffffff);} p {line-height: 1.5;}'),
        Div(
            H1(title), # Title
            P(desc), # Desc
            *L(nb.cells[2:]).map(StyledCell),
            cls="space-y-5"
        )
    )

http://localhost:8000/nbs/2025-01-23-Troubleshooting-MonsterUI-on-This-Site

name = "2025-01-23-Troubleshooting-MonsterUI-on-This-Site"
fpath = Path(f'{name}.ipynb')
fpath.absolute()

Troubleshooting the Troubleshooting MonsterUI Notebook

def is_code_cell(c): return c.cell_type == "code"
cells = L(read_nb(nb_paths[1]).cells).filter(is_code_cell)
cells
cells[1]
cells[1].outputs[0].data

Troubleshooting the Cosine Similarity Notebook

def is_code_cell(c): return c.cell_type == "code"
cells = L(read_nb(nb_paths[9]).cells).filter(is_code_cell)
cells[0]

Rendering Code Outputs With render_code_output

cells = L(read_nb(nb_paths[1]).cells).filter(is_code_cell)
cells[1]
render_code_output(cells[1])
# show(*L(read_nb(nb_paths[4]).cells).map(StyledCell))

Serve

#| export
serve()
server.stop()

Export

To export this notebook as arg-blog-fasthtml's main.py:

nb_export 2025-01-23-This-Site-Is-Now-Powered-by-This-Notebook.ipynb
mv main.py ..