from fasthtml.common import * from monsterui.all import * import frontmatter import pathlib from fasthtml.components import Uk_theme_switcher from monsterui.foundations import * app, rt = fast_app(hdrs=Theme.gray.headers(daisy=True)) def load_book(file_path): """Load and parse a book's markdown file""" with open(file_path) as f: post = frontmatter.load(f) return post def create_mode_picker(): def _opt(val, txt, **kwargs): return Option(txt, value=val, **kwargs) def _optgrp(key, lbl, opts): return Optgroup(data_key=key, label=lbl)(*opts) group = _optgrp('mode', '', [ _opt('light','',data_icon='sun'), _opt('dark','',data_icon='moon') ]) return Div(Uk_theme_switcher( Select(group, hidden=True, selected=True), id="mode-picker" ), cls="fixed top-4 right-4 z-50 p-2", # style="width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;" ) def create_book_page(post): """Create the detailed book page using our template""" metadata = post.metadata return Title(metadata["title"]), Container( random_theme_script(), DivCentered( Card( DivCentered( H1(metadata["title"], cls="text-transparent bg-clip-text bg-gradient-to-r from-primary via-muted to-primary transition-all duration-1000 hover:scale-105", style="font-size: 2.5rem; font-weight: 700; -webkit-text-stroke: 0.5px rgba(0, 0, 0, 0.7);"), H2(metadata["author"], cls=(TextT.muted)), A( Img( src=metadata['cover_img_url'], cls="rounded-lg hover:scale-105 shadow-lg transition-all duration-1000", style="width:300px" ), cls="rounded-lg overflow-hidden", href=metadata['book_url'] ), cls="text-center space-y-6" ), DivCentered( Section( DivHStacked( Label(f"📅 {metadata['date'].strftime('%B %d, %Y')}", cls=LabelT.secondary), Label(f"📚 {metadata['genre']}", cls=LabelT.secondary), cls="space-x-2" ), cls=SectionT.xs ), cls="mb-6" ), Div( render_md( post.content, class_map={ 'h3': f'text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary {TextT.xl} {TextT.bold} mb-6 mt-8', 'ul': f'{ListT.disc} space-y-4 mb-8', 'li': f'{TextT.lg} {TextT.normal} leading-relaxed', 'ul ul': f'{ListT.circle} ml-8 mt-4', 'ul ul li': f'{TextT.normal} leading-relaxed', 'p': f'{TextT.lg} {TextT.normal} mb-4', 'img': 'rounded-lg shadow-md hover:shadow-xl transition-shadow duration-200', '*[@class="gallery"]': 'flex flex-row items-center justify-center gap-8' } ), cls = 'space-y-6' ), cls=CardT.default ) ), cls=(ContainerT.lg, 'p-8'), style="position: relative; overflow: hidden;" ) def create_book_card(metadata, filename): """Create a card for the book listing""" return A( Card( DivCentered( Img( src=metadata['cover_img_url'], cls="rounded-lg shadow-md hover:scale-105 transition-all duration-300", style="width:200px" ), H3(metadata['title'], cls="text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary"), P(metadata['author'], cls=TextT.muted), DivHStacked( Label(metadata['genre'], cls=LabelT.secondary), Label(metadata['date'].strftime('%B %d, %Y'), cls=LabelT.secondary), cls="mt-4 space-x-2" ), cls="space-y-4 p-4" ), cls=(CardT.hover, "transition-all duration-300 hover:shadow-xl") ), href=f"/book/{filename}", ) def random_theme_script(): return Script(""" document.addEventListener('DOMContentLoaded', function() { const themes = ['uk-theme-zinc', 'uk-theme-slate', 'uk-theme-red', 'uk-theme-rose', 'uk-theme-orange', 'uk-theme-green', 'uk-theme-blue', 'uk-theme-yellow', 'uk-theme-violet']; const randomTheme = themes[Math.floor(Math.random() * themes.length)]; document.documentElement.className = randomTheme; }); """) @rt def index(): """Homepage with grid of book cards""" books_path = pathlib.Path('books') book_files = list(books_path.glob('*.md')) # Create cards for each book book_cards = [] for file in book_files: post = load_book(file) book_cards.append(create_book_card(post.metadata, file.stem)) return Title("NotesMD"), Container( create_mode_picker(), random_theme_script(), Grid(*book_cards, cols_sm=1, cols_md=2, cols_lg=3, cols_xl=4, gap=6), cls=(ContainerT.xl, 'p-8') ) @rt("/book/{filename}") def get(filename: str): """Individual book page""" try: books_path = pathlib.Path('books') book_file = books_path / f"{filename}.md" if not book_file.exists(): raise FileNotFoundError post = load_book(book_file) return create_book_page(post) except (FileNotFoundError, ValueError) as e: return Title("Not Found!"), Container( DivCentered( H1("Book Not Found", cls=TextT.error), P("Sorry, we couldn't find the book you're looking for."), A("Return to Library", href="/", cls=ButtonT.primary), cls="space-y-6 py-12" ) ) serve()